feat(lang): functions are values (L2), and render3d takes its scene as callbacks
fn(int, float) -> bool is a type, fn name is any top-level function's value, and a call through a local, a global, a record field, a slice element, a parameter or a result of a function type is an indirect call; two function types mix only when equal, a call checks its argument count, and a value may be null (examples/functions/values.ludic). Job.parallel_for keeps its worker check. render3d's scene is registered rather than required by name: r3d_on_draw, r3d_on_casters and r3d_on_stream_fill (hooks.ludic). The two rendering examples register theirs - and had defined scene_draw_casters with no parameter while the renderer passed one, which nothing checked. render3d declares numbers float itself; smooth.ludic is converted to floats and returns when r3d_init fails instead of running on into a segfault. Noise.* check their argument count (a call one short crashed the compiler). selfhost-build says why it failed. The migration tool reads a declared float as evidence. Seed regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
dc75a5a5ab
commit
0c73287e35
23 changed files with 71430 additions and 68575 deletions
|
|
@ -722,7 +722,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "error") { bare = "job_error"; push(labels, "handle") }
|
||||
if (meth == "pending") { bare = "job_pending" }
|
||||
if (meth == "free") { bare = "job_free"; push(labels, "handle") }
|
||||
if (meth == "parallel_for") { bare = "job_parallel_for"; push(labels, "count"); push(labels, "work"); push(labels, "ctx") }
|
||||
if (meth == "parallel_for") { bare = "job_parallel_for"; push(labels, "count"); push(labels, "work"); push(labels, "ctx"); if len(e.kids) > 1 { check_worker_ref(e.kids[1]) } }
|
||||
if (meth == "is_worker") { bare = "job_is_worker" }
|
||||
}
|
||||
if (ns == "Promise") {
|
||||
|
|
@ -1035,6 +1035,9 @@ function emit_variant_new(en: Node, ord: int, args: []Node) -> Val {
|
|||
}
|
||||
|
||||
function emit_call(e: Node) -> Val {
|
||||
# a call through a value of a function type (L2): a local, a global, a field, an element
|
||||
let fv = callee_value(e)
|
||||
if fv != null { return emit_indirect_call(fv, e) }
|
||||
# `Subject.action(...)` — a namespaced builtin (Screen/Random/Input).
|
||||
if e.a.kind == E_MEMBER {
|
||||
if e.a.a.kind == E_ID { return emit_ns_call(e.a.a.s, e.a.s, e) }
|
||||
|
|
@ -1462,14 +1465,7 @@ function emit_expr(e: Node) -> Val {
|
|||
if e.kind == E_LIST { return emit_list(e) } # [a, b, c] -> a fresh slice
|
||||
# fn name -> the function's address, for a worker entry point. The OS-thread runtime calls it
|
||||
# as void(i32, ptr), so that is the only signature a reference may have.
|
||||
if e.kind == E_FNREF {
|
||||
let d = find_fn(e.s)
|
||||
if d == null { perr(`fn {e.s}: no function called {e.s}`) }
|
||||
var ok = len(d.kids) == 2 and llty(d.ty) == "void"
|
||||
if ok { ok = llty(d.kids[0].ty) == "i32" and llty(d.kids[1].ty) == "ptr" }
|
||||
if not ok { perr(`fn {e.s}: a worker function takes (i: int, ctx: pointer) and returns nothing`) }
|
||||
return val(`@fn_{e.s}`, "pointer")
|
||||
}
|
||||
if e.kind == E_FNREF { return emit_fnref(e) }
|
||||
if e.kind == E_ID {
|
||||
let li = loc_find(e.s)
|
||||
if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) }
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ function to_fixed(v: Val) -> pointer {
|
|||
# narrows with trunc; everything else (same width, or ptr) passes through.
|
||||
function coerce_code(v: Val, target: pointer) -> pointer {
|
||||
if is_fp(target) { return to_fp(v, target, `a {target} slot`) }
|
||||
if is_fn_type(target) and is_fn_type(v.ty) and not (v.ty == target) { perr(`a {v.ty} is not a {target}`) }
|
||||
if (target == "fixed") and not (fixed_lit_code(v) == "") { return fixed_lit_code(v) }
|
||||
if is_fp(v.ty) and not is_fp(target) and (llty(target) != "ptr") {
|
||||
perr(`a {v.ty} does not convert to {target} implicitly — write int(x) or fixed(x)`)
|
||||
|
|
|
|||
131
selfhost/backend/emit_fnval.ludic
Normal file
131
selfhost/backend/emit_fnval.ludic
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
# emit_fnval.ludic — L2: functions as values. A function type is written `fn(int, float) -> bool`
|
||||
# and carried as the text "fn(int,float)->bool" (ptype normalises it); its value is the function's
|
||||
# address, `fn name` takes one, and a call through anything of a function type is an indirect call.
|
||||
|
||||
function is_fn_type(t: pointer) -> bool {
|
||||
if t == null { return false }
|
||||
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
|
||||
function fn_sig_of(d: Node) -> pointer {
|
||||
var out = "fn("
|
||||
var i = 0
|
||||
while i < len(d.kids) {
|
||||
if i > 0 { out = out + "," }
|
||||
out = out + d.kids[i].ty
|
||||
i += 1
|
||||
}
|
||||
var r = d.ty
|
||||
if r == null { r = "void" }
|
||||
return out + ")->" + r
|
||||
}
|
||||
# the parameter types of a function type: split at its top-level commas (a parameter may be a
|
||||
# function type itself, whose commas are inside its own parentheses)
|
||||
function fn_ty_params(t: pointer) -> []pointer {
|
||||
let out = new []pointer
|
||||
var depth = 0
|
||||
var start = 3
|
||||
var i = 3
|
||||
while t[i] != 0 {
|
||||
let c = t[i]
|
||||
if c == '(' { depth += 1 }
|
||||
if c == ')' {
|
||||
if depth == 0 {
|
||||
if i > start { push(out, t[start..i]) }
|
||||
return out
|
||||
}
|
||||
depth -= 1
|
||||
}
|
||||
if c == ',' and depth == 0 {
|
||||
push(out, t[start..i])
|
||||
start = i + 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
# the result type: everything after the ")->" that closes the parameters
|
||||
function fn_ty_ret(t: pointer) -> pointer {
|
||||
var depth = 0
|
||||
var i = 3
|
||||
while t[i] != 0 {
|
||||
if t[i] == '(' { depth += 1 }
|
||||
if t[i] == ')' {
|
||||
if depth == 0 { return t[i + 3..len(t)] }
|
||||
depth -= 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return "void"
|
||||
}
|
||||
# `fn name`: the function's address, typed by its signature
|
||||
function emit_fnref(e: Node) -> Val {
|
||||
let d = find_fn(e.s)
|
||||
if d == null { perr(`fn {e.s}: no function called {e.s}`) }
|
||||
return val(`@fn_{e.s}`, fn_sig_of(d))
|
||||
}
|
||||
# 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)
|
||||
let ret = fn_ty_ret(fv.ty)
|
||||
if len(e.kids) != len(ptys) { perr(`a {fv.ty} takes {itoa(len(ptys))} argument(s), and this call gives {itoa(len(e.kids))}`) }
|
||||
let args = new []pointer
|
||||
var i = 0
|
||||
while i < len(e.kids) {
|
||||
let v = emit_expr(e.kids[i])
|
||||
push(args, coerce_code(v, ptys[i]))
|
||||
i += 1
|
||||
}
|
||||
let rl = llty(ret)
|
||||
emit(" ")
|
||||
var rreg = "0"
|
||||
if not (rl == "void") {
|
||||
rreg = nreg()
|
||||
emit(rreg)
|
||||
emit(" = ")
|
||||
}
|
||||
emit(`call {rl} {fv.code}(`)
|
||||
i = 0
|
||||
while i < len(args) {
|
||||
if i > 0 { emit(", ") }
|
||||
emit(`{llty(ptys[i])} {args[i]}`)
|
||||
i += 1
|
||||
}
|
||||
emit(")\n")
|
||||
return val(rreg, ret)
|
||||
}
|
||||
# a callee that is a value rather than a name: a local, a global, a field or an element of a
|
||||
# function type. null when the callee names a function (or a namespace) the ordinary way.
|
||||
function callee_value(e: Node) -> Val {
|
||||
let c = e.a
|
||||
if c.kind == E_ID {
|
||||
let li = loc_find(c.s)
|
||||
if li >= 0 and is_fn_type(loc_ty[li]) { return emit_expr(c) }
|
||||
if li < 0 {
|
||||
let g = find_global(c.s)
|
||||
if g != null and is_fn_type(g.ty) { return emit_expr(c) }
|
||||
}
|
||||
return null
|
||||
}
|
||||
if c.kind == E_MEMBER {
|
||||
# `obj.field(...)`: obj is a local or a global, never a namespace; a field that is not a
|
||||
# function leaves the call to the ordinary path (loading it had no side effect)
|
||||
if c.a.kind == E_ID and loc_find(c.a.s) < 0 and find_global(c.a.s) == null { return null }
|
||||
let mv = emit_expr(c)
|
||||
if is_fn_type(mv.ty) { return mv }
|
||||
return null
|
||||
}
|
||||
let v = emit_expr(c)
|
||||
if not is_fn_type(v.ty) { perr(`a {v.ty} is not a function and cannot be called`) }
|
||||
return v
|
||||
}
|
||||
# Job.parallel_for runs its worker on OS threads that call it as void(i32, ptr)
|
||||
function check_worker_ref(e: Node) -> void {
|
||||
if e.kind != E_FNREF { return }
|
||||
let d = find_fn(e.s)
|
||||
if d == null { return }
|
||||
# the thread calls it as void(i32, ptr): any pointer-sized context will do (words, a record)
|
||||
var ok = len(d.kids) == 2 and llty(d.ty) == "void"
|
||||
if ok { ok = llty(d.kids[0].ty) == "i32" and llty(d.kids[1].ty) == "ptr" }
|
||||
if not ok { perr(`fn {e.s}: a worker function takes (i: int, ctx: pointer) and returns nothing`) }
|
||||
}
|
||||
|
|
@ -35,23 +35,28 @@ function emit_noise_ns(meth: pointer, e: Node) -> Val {
|
|||
}
|
||||
g_uses_noisert = true
|
||||
if (meth == "value2") {
|
||||
noise_arity(e, "value2", 3)
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @lp_noise_value2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
}
|
||||
if (meth == "perlin2") {
|
||||
noise_arity(e, "perlin2", 3)
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @lp_noise_perlin2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
}
|
||||
if (meth == "simplex2") {
|
||||
noise_arity(e, "simplex2", 3)
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @lp_noise_simplex2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
}
|
||||
if (meth == "fbm2") {
|
||||
noise_arity(e, "fbm2", 4)
|
||||
g_uses_mathrt = true # simplex path is standalone; fbm needs fx_div only (local)
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2]); let o = emit_expr(e.kids[3])
|
||||
return val(emit_bind(`call i32 @lp_noise_fbm2(i32 {x.code}, i32 {y.code}, i32 {s.code}, i32 {o.code})`), "fixed")
|
||||
}
|
||||
if (meth == "cellular2") {
|
||||
noise_arity(e, "cellular2", 3)
|
||||
g_uses_mathrt = true # F1 distance needs @lp_fx_sqrt
|
||||
let x = emit_expr(e.kids[0]); let y = emit_expr(e.kids[1]); let s = emit_expr(e.kids[2])
|
||||
return val(emit_bind(`call i32 @lp_noise_cellular2(i32 {x.code}, i32 {y.code}, i32 {s.code})`), "fixed")
|
||||
|
|
@ -215,3 +220,13 @@ function emit_noise_cellular() -> void {
|
|||
emith(" %d2 = alloca i32\n %id = alloca i32\n call void @lp_noise_cell_scan(i32 %x, i32 %y, i32 %seed, ptr %d2, ptr %id)\n")
|
||||
emith(" %r = load i32, ptr %id\n ret i32 %r\n}\n")
|
||||
}
|
||||
# Noise.* take their arguments by position, and fbm2's fourth is the octave count: a call one short
|
||||
# used to read past its arguments and crash the compiler - or, where the call happened to fit,
|
||||
# put the octaves in the seed and give a flat 0.5 everywhere
|
||||
function noise_arity(e: Node, meth: pointer, n: int) -> void {
|
||||
if len(e.kids) != n { perr(`Noise.{meth} takes {itoa(n)} arguments (x, y, seed{noise_arity_more(n)}), and this call gives {itoa(len(e.kids))}`) }
|
||||
}
|
||||
function noise_arity_more(n: int) -> pointer {
|
||||
if n == 4 { return ", octaves" }
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,27 @@ function eat_id() -> pointer {
|
|||
}
|
||||
function skipnl() -> void { while toks[pi].kind == TK_NL { pi += 1 } }
|
||||
|
||||
# a type: `[]T` slice, or a plain name (int/ptr/str/bool/struct)
|
||||
# a type: `[]T` slice, `fn(T, U) -> R` function, or a plain name (int/ptr/str/bool/struct)
|
||||
function ptype() -> pointer {
|
||||
if (toks[pi].text == "fn") and (toks[pi + 1].text == "(") {
|
||||
pi += 1
|
||||
eat_op("(")
|
||||
var out = "fn("
|
||||
var first = true
|
||||
while not is_op(")") {
|
||||
if not first { out = out + "," }
|
||||
out = out + ptype()
|
||||
first = false
|
||||
if is_op(",") { pi += 1 }
|
||||
}
|
||||
eat_op(")")
|
||||
var r = "void"
|
||||
if is_op("->") {
|
||||
pi += 1
|
||||
r = ptype()
|
||||
}
|
||||
return out + ")->" + r
|
||||
}
|
||||
if is_op("[") {
|
||||
pi += 1
|
||||
eat_op("]")
|
||||
|
|
|
|||
69762
selfhost/ludicc.seed.ll
69762
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue