Merge branch 'lang/memory-owned' into lang/foundations

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 17:04:40 +03:00
commit f16ac4ef9e
6 changed files with 74859 additions and 72140 deletions

View file

@ -229,6 +229,7 @@ function deps_keeps(f: pointer, seen: []bool) -> void {
if g_arena_strict and n > 0 { exit(1) }
deps_births(f, seen)
deps_resources(f) # 25.5e
deps_owned(f)
}
function fr_consumed(n: Node) -> bool {

View file

@ -10,6 +10,10 @@ var g_res_free_fn: []pointer = new []pointer
var g_res_free_kind: []pointer = new []pointer
var g_res_pending_make: pointer = null
var g_res_pending_free: pointer = null
var g_own_type: []pointer = new []pointer # @owns(Kind) fields: the record's type ...
var g_own_field: []pointer = new []pointer # ... the field ...
var g_own_kind: []pointer = new []pointer # ... and the kind of handle it owns
var g_own_pending: pointer = null
function res_kind_made_by(name: pointer) -> pointer {
var i = 0
@ -98,3 +102,86 @@ function deps_resources(f: pointer) -> void {
}
deps_line(f, `resource_drops {itoa(g_res_drops)}`)
}
function res_is_release(name: pointer) -> bool {
var i = 0
while i < len(g_res_free_fn) { if (g_res_free_fn[i] == name) { return true }; i += 1 }
return false
}
# every `base.field` handed to a call in n: into rel when the callee @releases, into all always
function own_collect(n: Node, rel_b: []pointer, rel_f: []pointer, all_b: []pointer, all_f: []pointer, rel_line: []int) -> void {
if n == null { return }
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID and n.kids != null {
let releasing = res_is_release(n.a.s)
var i = 0
while i < len(n.kids) {
let a = n.kids[i]
if a != null and a.kind == E_MEMBER and a.a != null and a.a.kind == E_ID and a.s != null {
push(all_b, a.a.s)
push(all_f, a.s)
if releasing {
push(rel_b, a.a.s)
push(rel_f, a.s)
push(rel_line, n.line)
}
}
i += 1
}
}
own_collect(n.a, rel_b, rel_f, all_b, all_f, rel_line)
own_collect(n.b, rel_b, rel_f, all_b, all_f, rel_line)
own_collect(n.c, rel_b, rel_f, all_b, all_f, rel_line)
if n.kids != null {
var j = 0
while j < len(n.kids) { own_collect(n.kids[j], rel_b, rel_f, all_b, all_f, rel_line); j += 1 }
}
}
function own_has(bs: []pointer, fs: []pointer, b: pointer, f: pointer) -> bool {
var i = 0
while i < len(bs) { if (bs[i] == b) and (fs[i] == f) { return true }; i += 1 }
return false
}
# 25.5e's owned fields: a function that releases one @owns field of a record (x.body) and neither
# releases nor hands on another @owns field of the same record type (x.shape) gives the first back
# and loses the second - phys_remove freeing the body and not its shape. `oleak` lines, owned_leaks
var g_own_leaks: int = 0
function deps_owned(f: pointer) -> void {
g_own_leaks = 0
if len(g_own_field) > 0 {
var i = 0
while i < len(prog) {
let d = prog[i]
if (d.kind == N_FN or d.kind == N_SYS) and d.file != null and not is_runtime_file(d.file) {
let rb = new []pointer
let rf = new []pointer
let rl = new []int
let ab = new []pointer
let af = new []pointer
own_collect(d.a, rb, rf, ab, af, rl)
var r = 0
while r < len(rb) {
var t = 0
while t < len(g_own_field) {
if (g_own_field[t] == rf[r]) {
var o = 0
while o < len(g_own_field) {
if o != t and (g_own_type[o] == g_own_type[t]) and not own_has(ab, af, rb[r], g_own_field[o]) {
deps_line(f, `oleak {g_own_type[o]}.{g_own_field[o]} {d.file}:{itoa(rl[r])} {d.s} {rb[r]}.{rf[r]}`)
g_own_leaks += 1
}
o += 1
}
}
t += 1
}
r += 1
}
}
i += 1
}
}
deps_line(f, `owned_leaks {itoa(g_own_leaks)}`)
}

View file

@ -15,6 +15,11 @@ function parse_component() -> Node {
pi += 1
let ann = eat_id()
if ann == "Computed" { is_computed = true } else if ann == "Sync" { is_sync = true } else if ann == "frame" { is_frame = true }
else if ann == "owns" { # @owns(PhysShape) — this field holds a handle its record owns (25.5e)
eat_op("(")
g_own_pending = eat_id()
eat_op(")")
}
else if ann == "max" {
eat_op("(")
let ce = expr()
@ -27,6 +32,7 @@ function parse_component() -> Node {
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
if is_frame { push(g_frame_fields, `{n.s}.{f.s}`) }
if cap > 0 { push(g_cap_keys, `{n.s}.{f.s}`); push(g_cap_vals, cap) }
if g_own_pending != null { push(g_own_type, n.s); push(g_own_field, f.s); push(g_own_kind, g_own_pending); g_own_pending = null }
if is_op("=") { pi += 1; f.a = expr() }
if is_sync { f.ival = 1 } # mark the field replicable (read by emit_net)
if is_computed { register_computed(n.s, f.s, f.ty, f.a) } # derived: no storage

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -60,6 +60,8 @@ var dp_fbirths: []pointer = new []pointer # 25.2: `fbirth <kind> <file:l
var dp_birth_leaks: int = 0
var dp_rdrops: []pointer = new []pointer # 25.5e: `rdrop <kind> <file:line> <fn> <how>`
var dp_resource_drops: int = 0
var dp_oleaks: []pointer = new []pointer # 25.5e: `oleak <Type.field> <file:line> <fn> <released>`
var dp_owned_leaks: int = 0
var dp_wreach_n: int = 0
var dp_wreach_at: pointer = ""
@ -102,6 +104,7 @@ function dp_load(path: pointer) -> bool {
dp_fkeeps = new []pointer; dp_frame_keeps = 0
dp_fbirths = new []pointer; dp_birth_leaks = 0
dp_rdrops = new []pointer; dp_resource_drops = 0
dp_oleaks = new []pointer; dp_owned_leaks = 0
dp_writes = new []pointer; dp_aliases = new []pointer; dp_wowner = new []pointer; dp_wname = new []pointer; dp_wfrom = new []pointer
let lines = split_lines(text)
for i in 0 .. len(lines) {
@ -131,6 +134,8 @@ function dp_load(path: pointer) -> bool {
if len(w) == 2 and w[0] == "birth_leaks" { dp_birth_leaks = s_to_int(w[1]) }
if len(w) >= 5 and w[0] == "rdrop" { push(dp_rdrops, lines[i]) }
if len(w) == 2 and w[0] == "resource_drops" { dp_resource_drops = s_to_int(w[1]) }
if len(w) >= 5 and w[0] == "oleak" { push(dp_oleaks, lines[i]) }
if len(w) == 2 and w[0] == "owned_leaks" { dp_owned_leaks = s_to_int(w[1]) }
if len(w) >= 5 and w[0] == "width" and w[4] == "0" and s_to_int(w[1]) > dp_wide_n {
dp_wide_n = s_to_int(w[1])
dp_wide_at = `{dp_fn_name(w[2])} ({w[3]})`
@ -330,7 +335,7 @@ var dp_vals: []int = null
var dp_cycle: []int = null
var dp_cycle_all: []int = null # the same with the layers' own edges counted
function dp_numbers() -> void {
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function", "widest_reach", "widest_write_reach", "frame_allocs", "frame_keeps", "birth_leaks", "resource_drops"]
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function", "widest_reach", "widest_write_reach", "frame_allocs", "frame_keeps", "birth_leaks", "resource_drops", "owned_leaks"]
var mods = 0
for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } }
var deps = 0
@ -352,7 +357,7 @@ function dp_numbers() -> void {
}
}
dp_reach_best()
dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n, dp_reach_n, dp_wreach_n, dp_frame_allocs, dp_frame_keeps, dp_birth_leaks, dp_resource_drops]
dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n, dp_reach_n, dp_wreach_n, dp_frame_allocs, dp_frame_keeps, dp_birth_leaks, dp_resource_drops, dp_owned_leaks]
}
# ---- the command --------------------------------------------------------------
@ -510,6 +515,7 @@ function cmd_deps() -> int {
else if a == "--keeps" { mode = "keeps" }
else if a == "--births" { mode = "births" }
else if a == "--resources" { mode = "resources" }
else if a == "--owned" { mode = "resources" }
else if a == "--uses" or a == "--check" or a == "--baseline" or a == "--widest" or a == "--reach" or a == "--wreach" {
if ai + 1 >= arg_count() { err(`ludic deps: {a} needs an argument\n`); return 2 }
mode = sslice(a, 2, slen(a))
@ -583,6 +589,11 @@ function cmd_deps() -> int {
print(`resource dropped: {w[1]} at {w[2]} in {w[3]}: {w[4]}`)
}
print(`resource_drops: {string(dp_resource_drops)}`)
for a in 0 .. len(dp_oleaks) {
let w = dp_words(dp_oleaks[a])
print(`owned field lost: {w[1]} at {w[2]} in {w[3]}, which releases {w[4]}`)
}
print(`owned_leaks: {string(dp_owned_leaks)}`)
return 0
}
if mode == "widest" { dp_top(s_to_int(arg2), 0); return 0 }