Merge branch 'lang/memory-gaps' into lang/foundations
This commit is contained in:
commit
1315baf332
6 changed files with 62210 additions and 59807 deletions
40
examples/lang/arena_pool.ludic
Normal file
40
examples/lang/arena_pool.ludic
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# arena_pool.ludic - a pool that keeps its records across frames through a generic push (the shape of
|
||||
# ludic.ui's nd_take and ui_kept). Built with --arena, a record the pool keeps must not come from the
|
||||
# frame's scratch: run with R3D_ARENA_CHECK=1 (each reset filled with 0xDD) it prints what it prints
|
||||
# with the arena off (R3D_ARENA=0) - `7 3498`.
|
||||
program ArenaPool {
|
||||
property Cell { v: int = 0, tag: string = "" }
|
||||
property Pool { cells: []Cell = new []Cell, used: int = 0 }
|
||||
function kept<T>(xs: []T, v: T) -> void { push(xs, v) }
|
||||
function take(p: Pool, f: int) -> Cell {
|
||||
if p.used < len(p.cells) {
|
||||
let c = p.cells[p.used]
|
||||
p.used += 1
|
||||
return c
|
||||
}
|
||||
let c = new Cell { v: f }
|
||||
kept(p.cells, c)
|
||||
p.used += 1
|
||||
return c
|
||||
}
|
||||
function step(p: Pool, f: int) -> int {
|
||||
p.used = 0
|
||||
var s = 0
|
||||
for k in 0 .. (f % 8) {
|
||||
let c = take(p, f)
|
||||
c.v = f + k
|
||||
s += c.v
|
||||
}
|
||||
return s
|
||||
}
|
||||
entry {
|
||||
let p = new Pool
|
||||
for f in 0 .. 500 {
|
||||
step(p, f)
|
||||
Mem.frame()
|
||||
}
|
||||
var sum = 0
|
||||
for i in 0 .. len(p.cells) { sum += p.cells[i].v }
|
||||
print(`{len(p.cells)} {sum}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +224,16 @@ function es_val(e: Node) -> int {
|
|||
var i = 0
|
||||
while i < len(e.a.kids) { es_store(es_ref(e.a.kids[i].a), s); i += 1 }
|
||||
}
|
||||
# a field's default is made with the record and held by it (`facts: Queue<F> = queue_new(...)`)
|
||||
let lay = find_comp(e.s)
|
||||
if lay != null and lay.kids != null {
|
||||
var j = 0
|
||||
while j < len(lay.kids) {
|
||||
let fd = lay.kids[j]
|
||||
if fd.a != null and not es_overridden(e, fd.s) { es_store(es_ref(fd.a), s) }
|
||||
j += 1
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
if k == E_LIST {
|
||||
|
|
@ -242,6 +252,39 @@ function es_val(e: Node) -> int {
|
|||
return g_es_heap
|
||||
}
|
||||
|
||||
function es_overridden(e: Node, field: pointer) -> bool {
|
||||
if e.a == null or e.a.kids == null { return false }
|
||||
var i = 0
|
||||
while i < len(e.a.kids) { if (e.a.kids[i].s == field) { return true }; i += 1 }
|
||||
return false
|
||||
}
|
||||
|
||||
# the function records a call to `nm` reaches: nm itself, or every instance of a generic nm
|
||||
function es_fns_for(nm: pointer) -> []int {
|
||||
let out = new []int
|
||||
let f = es_fn(nm)
|
||||
if f >= 0 {
|
||||
push(out, f)
|
||||
return out
|
||||
}
|
||||
let pre = `{nm}$`
|
||||
var i = 0
|
||||
while i < len(g_es_fnode) {
|
||||
let d = g_es_fnode[i]
|
||||
if d.s != null and str_starts(d.s, pre) { push(out, i) }
|
||||
i += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function es_keeps_nothing(nm: pointer) -> bool {
|
||||
if (nm == "quit") or (nm == "exit") or (nm == "panic") or (nm == "assert") or (nm == "expect") or (nm == "expect_eq") or (nm == "expect_near") { return true }
|
||||
if (nm == "min") or (nm == "max") or (nm == "abs") or (nm == "clamp") or (nm == "sqrt") or (nm == "floor") or (nm == "ceil") or (nm == "round") { return true }
|
||||
if (nm == "int") or (nm == "float") or (nm == "long") or (nm == "double") or (nm == "fixed") or (nm == "as_int") or (nm == "as_fixed") { return true }
|
||||
if (nm == "float_bits") or (nm == "float_from_bits") or (nm == "sin") or (nm == "cos") or (nm == "atan2") or (nm == "file_write") or (nm == "file_close") { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
function es_args_escape(e: Node) -> void {
|
||||
var i = 0
|
||||
while i < len(e.kids) { es_escape(es_val(e.kids[i])); i += 1 }
|
||||
|
|
@ -293,28 +336,49 @@ function es_call(e: Node) -> int {
|
|||
return -1
|
||||
}
|
||||
if (nm == "len") { es_args_walk(e); return -1 }
|
||||
let f = es_fn(nm)
|
||||
if f >= 0 {
|
||||
if (nm == "view") and len(e.kids) >= 1 { # a view shares its list's storage
|
||||
let base = es_val(e.kids[0])
|
||||
var vi = 1
|
||||
while vi < len(e.kids) { es_val(e.kids[vi]); vi += 1 }
|
||||
return base
|
||||
}
|
||||
# a generic's call names the generic; its instances (ui_kept$UiNode) are what is analysed, and
|
||||
# the call reaches every one of them
|
||||
let fs = es_fns_for(nm)
|
||||
if len(fs) > 0 {
|
||||
let r = es_new(0)
|
||||
var fi = 0
|
||||
while fi < len(fs) {
|
||||
let f = fs[fi]
|
||||
push(g_es_calls_f, g_es_cur)
|
||||
push(g_es_calls_t, f)
|
||||
let d = g_es_fnode[f]
|
||||
es_flow(g_es_fret[f], r)
|
||||
fi += 1
|
||||
}
|
||||
var i = 0
|
||||
while i < len(e.kids) {
|
||||
let v = es_ref(e.kids[i])
|
||||
var fj = 0
|
||||
while fj < len(fs) {
|
||||
let d = g_es_fnode[fs[fj]]
|
||||
if i < len(d.kids) and d.kids[i].kind == N_PARAM {
|
||||
if not es_prim(d.kids[i].ty) { es_flow(v, g_es_fparam[f] + i) }
|
||||
if not es_prim(d.kids[i].ty) { es_flow(v, g_es_fparam[fs[fj]] + i) }
|
||||
} else { es_escape(v) }
|
||||
fj += 1
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
let r = es_new(0)
|
||||
es_flow(g_es_fret[f], r)
|
||||
return r
|
||||
}
|
||||
# an intrinsic or a runtime builtin of the emitter's own: it keeps nothing it is handed, and
|
||||
# what it hands back is its own
|
||||
# an intrinsic known to keep nothing it is handed; anything else unknown keeps it all - the
|
||||
# arena is only as safe as this default
|
||||
if es_keeps_nothing(nm) {
|
||||
es_args_walk(e)
|
||||
return g_es_heap
|
||||
}
|
||||
es_args_escape(e)
|
||||
return g_es_heap
|
||||
}
|
||||
if es_is_mem_frame(e) {
|
||||
if g_es_cur >= 0 { g_es_fspans[g_es_cur] = true }
|
||||
return -1
|
||||
|
|
@ -541,6 +605,18 @@ function escape_analyse() -> void {
|
|||
f += 1
|
||||
}
|
||||
g_es_cur = -1
|
||||
# a global's initializer - a state's instance, a module's table - is made to be kept
|
||||
var gi = 0
|
||||
while gi < len(prog) {
|
||||
let gd = prog[gi]
|
||||
if gd.kind == N_VAR and gd.a != null {
|
||||
g_es_ln = new []pointer
|
||||
g_es_lc = new []int
|
||||
g_es_lt = new []pointer
|
||||
es_escape(es_ref(gd.a))
|
||||
}
|
||||
gi += 1
|
||||
}
|
||||
# an @On body keeps nothing it makes unless it stores it; it has no function record of its own
|
||||
var li = 0
|
||||
while li < len(g_onlisten) {
|
||||
|
|
@ -557,7 +633,9 @@ function escape_analyse() -> void {
|
|||
let c = g_es_site_cls[s]
|
||||
let fi = g_es_site_fn[s]
|
||||
let spans = fi < 0 or g_es_fspans[fi]
|
||||
if c >= 0 and (g_es_flag[c] & ES_ESC) == 0 and not spans {
|
||||
# LOCAL only when it can neither be kept (ESC) nor share storage with what is (HEAP): a push's
|
||||
# growth into a parameter's list is the list's own buffer, and the list may be a state's
|
||||
if c >= 0 and (g_es_flag[c] & (ES_ESC | ES_HEAP)) == 0 and not spans {
|
||||
n.uns = ES_SCRATCH
|
||||
g_es_local_n += 1
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -151,6 +151,7 @@ function deps_frame(f: pointer) -> void {
|
|||
let d = prog[i]
|
||||
if d.kind == N_SYS and d.s != null and fr_phase_frame(d.ty) { fr_mark_root(d.s) }
|
||||
if d.kind == N_FN and d.s != null and str_starts(d.s, "ludic_reduce__") { fr_mark_root(d.s) }
|
||||
if d.kind == N_FN and d.s != null and d.cm >= 0 { fr_mark_root(d.s) } # a component's own: it runs while its page is open
|
||||
fr_find_roots(d)
|
||||
i += 1
|
||||
}
|
||||
|
|
|
|||
60942
selfhost/ludicc.seed.ll
60942
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -817,6 +817,17 @@ function alloc_fence_auto_case() -> void {
|
|||
else { bad2(lbl, `rc={string(rc)} head=[{head}] count=[{through}]`) }
|
||||
}
|
||||
|
||||
# 25.3: a pool that keeps records across frames through a generic push must not take them from the
|
||||
# frame's scratch - poisoned on every reset it prints what the heap does
|
||||
function arena_pool_case() -> void {
|
||||
let lbl = "arena_pool.ludic (--arena: a generic push into a kept pool keeps its records and its buffer on the heap; 0xDD poison changes nothing)"
|
||||
let b = `{tmp_dir()}/e_arena_pool`
|
||||
if not shq(`bin/ludicc --arena examples/lang/arena_pool.ludic -o {b} 2>/dev/null`) { bad2(lbl, "did not build"); return }
|
||||
let poisoned = capture_line(`R3D_ARENA_CHECK=1 R3D_ALLOC_FENCE=off {b} < /dev/null`)
|
||||
let heap = capture_line(`R3D_ARENA=0 R3D_ALLOC_FENCE=off {b} < /dev/null`)
|
||||
if (poisoned == "7 3498") and (heap == "7 3498") { ok(lbl) } else { bad2(lbl, `poisoned [{poisoned}] heap [{heap}]`) }
|
||||
}
|
||||
|
||||
# issue #45: `bin/ludic-dev test --coverage`. Compile each test-spec with `--coverage`,
|
||||
# run it with LUDIC_COVERAGE pointed at a per-file dump, then aggregate the dumps
|
||||
# into a clean per-file line-coverage report. The instrumentation is flag-gated,
|
||||
|
|
@ -1115,6 +1126,7 @@ function cmd_dev_test() -> int {
|
|||
feat_case("lang/alloc_fence", "", "frames 300 kept 0 bad 0", "alloc_fence.ludic (25.1: a frame that makes only what it frees or reuses keeps nothing, and the fence passes it)")
|
||||
alloc_fence_leak_case()
|
||||
alloc_fence_auto_case()
|
||||
arena_pool_case()
|
||||
feat_case("lang/nested_templates", "", "outer [inner 3 {x}] \"`}\" end", "nested_templates.ludic (a template literal inside another's hole)")
|
||||
feat_case("lang/aliases", "", "10 5 3", "aliases.ludic (L6: a namespace method declared as an alias of a function, labelled or by its parameters)")
|
||||
reject_case("rejected/alias_arity", "this call to Trail.length leaves out to, which has no default", "an alias's arguments are checked against its target")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue