# emit_resource.ludic — 25.5e: a resource (a Jolt shape, a body, a GPU buffer) is a handle a function # @creates(Kind) makes and one @releases(Kind) gives back. A handle made and forgotten - the result of # a creating call thrown away, or bound to a local that is never released, passed on, stored or # returned - is never given back: an `rdrop` line, and resource_drops, which ludic deps ratchets. # (Two owners and a record's owned fields - the rest of 25.5e - wait on a resource type.) var g_res_make_fn: []pointer = new []pointer var g_res_make_kind: []pointer = new []pointer 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 while i < len(g_res_make_fn) { if (g_res_make_fn[i] == name) { return g_res_make_kind[i] }; i += 1 } return null } # the creating call an expression is, if it is one function res_made(e: Node) -> pointer { if e == null or e.kind != E_CALL or e.a == null or e.a.kind != E_ID or e.a.s == null { return null } return res_kind_made_by(e.a.s) } # is the local `name` handed on anywhere in n: an argument, a stored or returned value, an assignment's # right side (anything but a read into a comparison or an arithmetic) function res_handed(n: Node, name: pointer) -> bool { if n == null { return false } if n.kind == E_CALL and n.kids != null { var i = 0 while i < len(n.kids) { let a = n.kids[i] if a != null and a.kind == E_ID and (a.s == name) { return true } i += 1 } } if (n.kind == S_RETURN or n.kind == S_ASSIGN) { var v = n.a if n.kind == S_ASSIGN { v = n.b } if v != null and v.kind == E_ID and (v.s == name) { return true } } if n.kind == E_REC and n.kids != null { var j = 0 while j < len(n.kids) { let f = n.kids[j] if f.a != null and f.a.kind == E_ID and (f.a.s == name) { return true } j += 1 } } if n.kind == E_LIST and n.kids != null { var l = 0 while l < len(n.kids) { if n.kids[l].kind == E_ID and (n.kids[l].s == name) { return true }; l += 1 } } if res_handed(n.a, name) or res_handed(n.b, name) or res_handed(n.c, name) { return true } if n.kids != null { var k = 0 while k < len(n.kids) { if res_handed(n.kids[k], name) { return true }; k += 1 } } return false } var g_res_drops: int = 0 function res_walk(f: pointer, d: Node, n: Node) -> void { if n == null { return } if n.kind == S_EXPR and n.a != null { let kind = res_made(n.a) if kind != null { deps_line(f, `rdrop {kind} {d.file}:{itoa(n.line)} {d.s} thrown-away`) g_res_drops += 1 } } if n.kind == S_LET and n.s != null { let kind = res_made(n.a) if kind != null and not res_handed(d.a, n.s) { deps_line(f, `rdrop {kind} {d.file}:{itoa(n.line)} {d.s} {n.s}-never-handed-on`) g_res_drops += 1 } } res_walk(f, d, n.a) res_walk(f, d, n.b) res_walk(f, d, n.c) if n.kids != null { var i = 0 while i < len(n.kids) { res_walk(f, d, n.kids[i]); i += 1 } } } function deps_resources(f: pointer) -> void { g_res_drops = 0 if len(g_res_make_fn) > 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) { res_walk(f, d, d.a) } i += 1 } } 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)}`) }