Merge lang/foundations 69d1db0 into lang/ecs

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 16:41:55 +03:00
commit 23d204b9fc
22 changed files with 83199 additions and 77641 deletions

View file

@ -82,6 +82,9 @@ function actor_init(render3d_st: mut Render3dState) -> void {
# record, matrix and tint then (actor_release gives them back to this pool)
if render3d_st.ac_spare == null { render3d_st.ac_spare = new []Actor }
let sp = render3d_st.ac_spare
if render3d_st.ac_actors == null { render3d_st.ac_actors = new []Actor }
actor_room(render3d_st.ac_actors, 4 * ACTOR_POOL)
actor_room(sp, 4 * ACTOR_POOL)
for i in 0 .. ACTOR_POOL {
let a = new Actor
a.tint = v3_new(1.0, 1.0, 1.0)
@ -94,7 +97,8 @@ function actor_init(render3d_st: mut Render3dState) -> void {
render3d_st.ac_sh_cut = ac_prog_new(render3d_st, "skin.vert", "shadow.frag", "#define SHADOW_PASS\n#define ALPHA_TEST\n")
render3d_st.ac_out = ac_prog_new(render3d_st, "skin.vert", "outline.frag", "#define OUTLINE\n")
render3d_st.ac_out_cut = ac_prog_new(render3d_st, "skin.vert", "outline.frag", "#define OUTLINE\n#define ALPHA_TEST\n")
render3d_st.ac_actors = new []Actor
let stage = render3d_st.ac_actors # made above with its room; a new valley starts empty
List.clear(stage)
}
# off the stage: the last actor takes its row, so the list stays dense and nothing is rebuilt
# (it was a new list of every other actor per removal, never freed)
@ -475,3 +479,10 @@ function actor_own_skin(a: Actor) -> Skin {
a.skin_owned = true
return a.skin
}
# room for n more actors in a list, made at start-up: filled and emptied, it keeps what it grew to
function actor_room(l: []Actor, n: int) -> void {
let have = len(l)
for i in 0 .. n { push(l, null) }
while len(l) > have { List.pop(l) }
}

View file

@ -692,6 +692,7 @@ export state Render3dState {
stream_debug_n: int = 0
stream_scratch: floats = null
gvk_pipe_bufs: words = null # a pipeline's vertex buffers, while it is made
gsl_up: floats = floats(3) # the camera's up for DLSS's constants
ov_nine_buf: floats = floats(16) # ov_nine's corners and uvs
gvk_ac: pointer = null # the VkAllocationCallbacks every create and destroy is given (R3D_ALLOC_VK=1)
gvk_prime_b: words = null # buffers made since the frame began, read once so MoltenVK makes their

View file

@ -311,7 +311,7 @@ function gvk_alloc(render3d_st: mut Render3dState, req: bytes, want: int) -> lon
# device-local is a preference; host-visible is a need
if t < 0 and want == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT { t = gvk_mem_type(render3d_st, allowed, 0) }
let zero: long = 0
if t < 0 { print(`r3d: vulkan: no memory type for properties {want}`); return zero }
if t < 0 { gvk_say_no_type(want); return zero }
let mai = gvk_tmp(render3d_st, VkMemoryAllocateInfo_sizeof)
Vk.zero(mai, VkMemoryAllocateInfo_sizeof)
Vk.put_i32(mai, VkMemoryAllocateInfo_sType, VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO)
@ -320,7 +320,7 @@ function gvk_alloc(render3d_st: mut Render3dState, req: bytes, want: int) -> lon
let out = gvk_tmp(render3d_st, 8)
render3d_st.gvk_mk_mem += 1
let r = Vk.allocate_memory(render3d_st.gvk_dev, mai, render3d_st.gvk_ac, out)
if r != VK_SUCCESS { print(`r3d: vulkan: vkAllocateMemory failed (VkResult {r})`); return zero }
if r != VK_SUCCESS { gvk_say_alloc_failed(r); return zero }
render3d_st.gvk_n_allocs += 1
return gvk_handle(out)
}
@ -548,7 +548,7 @@ const GVK_TMP_BYTES: int = 1048576
function gvk_tmp(render3d_st: mut Render3dState, n: int) -> pointer {
let sz = (n + 15) / 16 * 16
if sz > GVK_TMP_BYTES { print(`r3d: vulkan: {n} bytes of scratch asked for at once`); return null }
if sz > GVK_TMP_BYTES { gvk_say_tmp_big(n); return null }
if render3d_st.gvk_tmp_off + sz > GVK_TMP_BYTES { render3d_st.gvk_tmp_off = 0 }
let p = mem_off(render3d_st.gvk_tmp_buf, render3d_st.gvk_tmp_off)
render3d_st.gvk_tmp_off += sz
@ -746,6 +746,18 @@ function gvk_startup_state(render3d_st: mut Render3dState) -> void {
if render3d_st.gvk_buf_spare == null { render3d_st.gvk_buf_spare = new []int }
if render3d_st.gvk_retired_frame == null { render3d_st.gvk_retired_frame = new []int }
if render3d_st.gvk_buf_gpu == null { render3d_st.gvk_buf_gpu = new []int }
if render3d_st.gvk_retired_buf == null { render3d_st.gvk_retired_buf = new []long; render3d_st.gvk_retired_mem = new []long }
# room made now in every list play pushes into, so a push never grows one (plan 25.2)
gvk_room_int(render3d_st.gvk_retired_frame, GVK_LIST_ROOM)
gvk_room_long(render3d_st.gvk_retired_buf, GVK_LIST_ROOM)
gvk_room_long(render3d_st.gvk_retired_mem, GVK_LIST_ROOM)
gvk_room_int(render3d_st.gvk_fr_blk, GVK_LIST_ROOM)
gvk_room_int(render3d_st.gvk_fr_off, GVK_LIST_ROOM)
gvk_room_int(render3d_st.gvk_fr_len, GVK_LIST_ROOM)
gvk_room_int(render3d_st.gvk_al_spare, GVK_LIST_ROOM)
gvk_room_int(render3d_st.gvk_tex_spare, GVK_LIST_ROOM)
gvk_room_int(render3d_st.gvk_buf_spare, GVK_LIST_ROOM)
gvk_room_int(render3d_st.gvk_buf_gpu, 4 * GVK_LIST_ROOM)
render3d_st.gvk_sc_tmp = new []long
let sct = render3d_st.gvk_sc_tmp
for i in 0 .. 130 { push(sct, zero) }
@ -768,3 +780,27 @@ function gvk_startup_state(render3d_st: mut Render3dState) -> void {
for i in 0 .. 32 { render3d_st.gpu_unit_2d[i] = -1 }
}
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_no_type(want: int) -> void { print(`r3d: vulkan: no memory type for properties {want}`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_alloc_failed(r: int) -> void { print(`r3d: vulkan: vkAllocateMemory failed (VkResult {r})`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_tmp_big(n: int) -> void { print(`r3d: vulkan: {n} bytes of scratch asked for at once`) }
const GVK_LIST_ROOM: int = 4096
# a list's room for n more, made now: filled and emptied again, it keeps what it grew to
function gvk_room_int(l: []int, n: int) -> void {
if l == null { return }
let have = len(l)
for i in 0 .. n { push(l, 0) }
while len(l) > have { List.pop(l) }
}
function gvk_room_long(l: []long, n: int) -> void {
if l == null { return }
let have = len(l)
let zero: long = 0
for i in 0 .. n { push(l, zero) }
while len(l) > have { List.pop(l) }
}

View file

@ -1113,7 +1113,7 @@ function gvk_pass_no_compressed(render3d_st: mut Render3dState) -> void {
if tex > 0 and tex < len(render3d_st.gvk_tex_glfmt) and gvk_is_compressed(render3d_st.gvk_tex_glfmt[tex]) {
if render3d_st.gvk_bc_said < 8 {
render3d_st.gvk_bc_said += 1
print(`r3d: vulkan: framebuffer {render3d_st.gvk_fb_cur} would draw into compressed texture {tex} ({gvk_tex_w(render3d_st, tex)}x{gvk_tex_h(render3d_st, tex)}); left out`)
gvk_say_compressed(render3d_st, tex)
}
} else {
render3d_st.gvk_pass_col[w * 2] = tex; render3d_st.gvk_pass_col[w * 2 + 1] = render3d_st.gvk_pass_col[c * 2 + 1]
@ -1269,7 +1269,7 @@ function gvk_draw(render3d_st: mut Render3dState, p: int, m: Mesh, st: GvkState,
# rejected every skinned one) otherwise leaves a whole layer missing from the frame in silence.
if p < 4096 and render3d_st.gvk_skip_said[p] == 0 {
render3d_st.gvk_skip_said[p] = 1
print(`r3d: vulkan: no pipeline for {gpu_program_key(render3d_st, p)}; its draws are skipped`)
gvk_say_no_pipeline(render3d_st, p)
}
return
}
@ -1835,7 +1835,7 @@ function gvk_resize_check(render3d_st: mut Render3dState) -> bool {
if w <= 0 or h <= 0 { return false }
if w == gl_width() and h == gl_height() and not render3d_st.gvk_swap_stale { return false }
# a remake is rare (a resize); said, so one on every frame shows in the log
print(`r3d: vulkan: swapchain remade {w}x{h} (was {gl_width()}x{gl_height()}, stale {render3d_st.gvk_swap_stale})`)
gvk_say_swap_remade(render3d_st, w, h)
gvk_flush(render3d_st)
gl_set_drawable(w, h)
gvk_screen_make(render3d_st, w, h)
@ -1986,3 +1986,11 @@ function gvk_pipe_build(render3d_st: mut Render3dState, p: int, m: Mesh, st: Gvk
if p < 4096 { render3d_st.gvk_pc_last[p] = len(render3d_st.gvk_pc_prog) - 1 }
return pipe
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_compressed(render3d_st: Render3dState, tex: int) -> void { print(`r3d: vulkan: framebuffer {render3d_st.gvk_fb_cur} would draw into compressed texture {tex} ({gvk_tex_w(render3d_st, tex)}x{gvk_tex_h(render3d_st, tex)}); left out`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_no_pipeline(render3d_st: Render3dState, p: int) -> void { print(`r3d: vulkan: no pipeline for {gpu_program_key(render3d_st, p)}; its draws are skipped`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_swap_remade(render3d_st: Render3dState, w: int, h: int) -> void { print(`r3d: vulkan: swapchain remade {w}x{h} (was {gl_width()}x{gl_height()}, stale {render3d_st.gvk_swap_stale})`) }

View file

@ -327,7 +327,7 @@ function gvk_tex_upload(render3d_st: mut Render3dState, tex: int, ifmt: int, w:
let hv = gvk_half(Vk.get_i32(src, i))
buf[o] = hv & 255; buf[o + 1] = (hv >> 8) & 255
} else {
print(`r3d: vulkan: no conversion from {bin}-byte to {bout}-byte samples`)
gvk_say_no_conversion(bin, bout)
gvk_staging_free(render3d_st)
return false
}
@ -448,7 +448,7 @@ function gvk_tex_read(render3d_st: mut Render3dState, tex: int, ifmt: int, w: in
# a read may ask for fewer channels than the image has (the height field's R of an RGBA32F
# bake), never more, and only in the sample size the image stores
if cwant > cout or gvk_gl_type_bytes(ty) != bout {
print(`r3d: vulkan: no read-back conversion for GL format {ifmt} as {fmt}/{ty}`)
gvk_say_no_readback(ifmt, fmt, ty)
return false
}
let n = w * h * cout * bout
@ -741,7 +741,7 @@ function gvk_buf_reserve(render3d_st: mut Render3dState, b: int, n: int) -> bool
let out = gvk_tmp(render3d_st, 8)
render3d_st.gvk_mk_buf += 1
var r = Vk.create_buffer(render3d_st.gvk_dev, bci, render3d_st.gvk_ac, out)
if r != VK_SUCCESS { return gvk_fail(render3d_st, `vkCreateBuffer ({size} bytes)`, r) }
if r != VK_SUCCESS { return gvk_fail_create_buffer(render3d_st, size, r) }
let buf = gvk_handle(out)
let req = gvk_tmp(render3d_st, VkMemoryRequirements_sizeof)
Vk.get_buffer_memory_requirements(render3d_st.gvk_dev, buf, req)
@ -831,3 +831,11 @@ function gvk_buf_upload(render3d_st: mut Render3dState, b: int, n: int, data: po
if data != null and n > 0 { mem_copy(render3d_st.gvk_buf_map[b], data, n) }
return true
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_no_conversion(bin: int, bout: int) -> void { print(`r3d: vulkan: no conversion from {bin}-byte to {bout}-byte samples`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_say_no_readback(ifmt: int, fmt: int, ty: int) -> void { print(`r3d: vulkan: no read-back conversion for GL format {ifmt} as {fmt}/{ty}`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gvk_fail_create_buffer(render3d_st: mut Render3dState, size: int, r: int) -> bool { return gvk_fail(render3d_st, `vkCreateBuffer ({size} bytes)`, r) }

View file

@ -57,6 +57,12 @@ function post_set_msaa(render3d_st: mut Render3dState, n: int) -> void {
@alloc_ok("a world being set up (a stream, a layer, water, post, a bake): the map loading or swapping, not a frame")
function post_init(render3d_st: mut Render3dState, w: int, h: int) -> void {
render3d_st.post_w = w; render3d_st.post_h = h
# the exposure's two 1x1 targets: made once, here, rather than by the first frame that measured
if render3d_st.post_adapt_t == null {
render3d_st.post_adapt_t = new []Target
for k in 0 .. 2 { push(render3d_st.post_adapt_t, target_new(render3d_st, 1, 1, GL_R32F, GL_RED, GL_FLOAT, false, GL_NEAREST)) }
render3d_st.post_adapt_reset = true
}
render3d_st.post_hdr = target_new(render3d_st, w, h, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR)
if render3d_st.post_ms_samples > 1 {
render3d_st.post_ms_fbo = gpu_fb_new(render3d_st)
@ -134,11 +140,6 @@ function post_measure(render3d_st: mut Render3dState) -> void {
gpu_tex_bind(render3d_st, GPU_TEX2D, render3d_st.post_hdr.color)
gpu_tex_param(render3d_st, GPU_TEX2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
gpu_tex_mips(render3d_st, GPU_TEX2D)
if render3d_st.post_adapt_t == null {
render3d_st.post_adapt_t = new []Target
for k in 0 .. 2 { push(render3d_st.post_adapt_t, target_new(render3d_st, 1, 1, GL_R32F, GL_RED, GL_FLOAT, false, GL_NEAREST)) }
render3d_st.post_adapt_reset = true
}
if render3d_st.post_p_adapt == 0 { render3d_st.post_p_adapt = r3d_program(render3d_st, "fullscreen.vert", "adapt.frag", "") }
let next = 1 - render3d_st.post_adapt_i
target_bind(render3d_st, render3d_st.post_adapt_t[next])

View file

@ -163,7 +163,7 @@ function r3d_resize(render3d_st: mut Render3dState) -> void {
post_free(render3d_st)
post_init(render3d_st, gl_width(), gl_height())
if render3d_st.water_refl != null { target_free(render3d_st, render3d_st.water_refl); render3d_st.water_refl = null }
print(`r3d: resized to {gl_width()}x{gl_height()}`)
r3d_say_resized()
}
function r3d_frame(render3d_st: mut Render3dState, time: float) -> void {
outline_frame(render3d_st)
@ -196,7 +196,7 @@ function r3d_frame(render3d_st: mut Render3dState, time: float) -> void {
# camera that moves while the hiker stands still from a picture that shakes on its own
if render3d_st.r3d_cam_log < 0 { render3d_st.r3d_cam_log = 0; if r3d_env_has(render3d_st, "R3D_CAM_LOG") { render3d_st.r3d_cam_log = 1 } }
if render3d_st.r3d_cam_log == 1 {
print(`cam {render3d_st.r3d_test_frame} pos {int(render3d_st.cam_pos[0] * 1000.0)} {int(render3d_st.cam_pos[1] * 1000.0)} {int(render3d_st.cam_pos[2] * 1000.0)} yaw {int(render3d_st.cam_yaw * 1000.0)} pitch {int(render3d_st.cam_pitch * 1000.0)} jitter {int(render3d_st.gsl_jitter_x * 1000000.0)} {int(render3d_st.gsl_jitter_y * 1000000.0)} render {render3d_st.post_w}x{render3d_st.post_h} reset {render3d_st.gsl_reset} evalok {render3d_st.gsl_eval_ok} fresh {render3d_st.gsl_fresh}`)
r3d_say_test_frame(render3d_st)
}
# the height-field shadow rebakes as the light moves in steps (daylight), or with the sky yaw when there is no clock
if not render3d_st.r3d_plate and ((not render3d_st.day_on and render3d_st.ter_shadow_yaw != render3d_st.sky_yaw) or render3d_st.ter_shadow_gen != render3d_st.day_gen) {
@ -285,3 +285,9 @@ function r3d_frame(render3d_st: mut Render3dState, time: float) -> void {
prof_collect(render3d_st)
gpu_check(render3d_st, "frame")
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function r3d_say_resized() -> void { print(`r3d: resized to {gl_width()}x{gl_height()}`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function r3d_say_test_frame(render3d_st: Render3dState) -> void { print(`cam {render3d_st.r3d_test_frame} pos {int(render3d_st.cam_pos[0] * 1000.0)} {int(render3d_st.cam_pos[1] * 1000.0)} {int(render3d_st.cam_pos[2] * 1000.0)} yaw {int(render3d_st.cam_yaw * 1000.0)} pitch {int(render3d_st.cam_pitch * 1000.0)} jitter {int(render3d_st.gsl_jitter_x * 1000000.0)} {int(render3d_st.gsl_jitter_y * 1000000.0)} render {render3d_st.post_w}x{render3d_st.post_h} reset {render3d_st.gsl_reset} evalok {render3d_st.gsl_eval_ok} fresh {render3d_st.gsl_fresh}`) }

View file

@ -40,7 +40,7 @@ function shadow_set_res(render3d_st: mut Render3dState, r: int) -> void {
# that is refused now, rather than ending with no shadow map at all
if not gpu_tex_ok(render3d_st, render3d_st.sh_tex) {
render3d_st.shadow_refused = r
print(`r3d: shadows: no memory for {r} x {r} cascades; keeping {was}`)
shadow_say_no_memory(r, was)
var size = was
render3d_st.shadow_res = size
gpu_tex_free(render3d_st, render3d_st.sh_tex)
@ -283,3 +283,7 @@ function shadow_debug_range(render3d_st: mut Render3dState, prog: int) -> void {
render3d_st.sh_printed3 = true
print(`range {fixed(render3d_st.sh_range[0])} {fixed(render3d_st.sh_range[1])} {fixed(render3d_st.sh_range[2])} {fixed(render3d_st.sh_range[3])} texel*1000 {fixed(render3d_st.sh_texel[0] * 1000.0)} {fixed(render3d_st.sh_texel[1] * 1000.0)} {fixed(render3d_st.sh_texel[2] * 1000.0)} {fixed(render3d_st.sh_texel[3] * 1000.0)} locs {gpu_uniform(render3d_st, prog, "u_cascade_range")} {gpu_uniform(render3d_st, prog, "u_cascade_texel")}`)
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function shadow_say_no_memory(r: int, was: int) -> void { print(`r3d: shadows: no memory for {r} x {r} cascades; keeping {was}`) }

View file

@ -254,7 +254,7 @@ function stream_update(render3d_st: mut Render3dState, s: Stream, cam_x: float,
let dt = gl_now_us() - t0
render3d_st.stream_us_gen = render3d_st.stream_us_gen + dt
prof_chunk(render3d_st, s.kind, band, c.count, dt)
if render3d_st.r3d_debug and band == 0 and render3d_st.stream_debug_n < 40 { render3d_st.stream_debug_n += 1; print(`stream kind {s.kind} band {band} chunk {cx},{cz}: {c.count} instances`) }
if render3d_st.r3d_debug and band == 0 and render3d_st.stream_debug_n < 40 { render3d_st.stream_debug_n += 1; stream_say_chunk(s.kind, band, cx, cz, c.count) }
let need = c.count * INST_FLOATS
if s.top + need > len(s.arena) and not render3d_st.stream_no_evict { stream_evict(render3d_st, s) }
# If the walk in progress wants more than the cache can hold, there is nothing to
@ -354,3 +354,7 @@ function stream_clear_all(render3d_st: mut Render3dState) -> void {
let all = render3d_st.stream_all
List.clear(all)
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function stream_say_chunk(kind: int, band: int, cx: int, cz: int, n: int) -> void { print(`stream kind {kind} band {band} chunk {cx},{cz}: {n} instances`) }

View file

@ -68,6 +68,8 @@ function gsl_boot(render3d_st: mut Render3dState) -> void {
# After Vk.open(), before vkCreateInstance.
@alloc_ok("start-up: the device, its tables, the programs, the passes and the world's first textures are made once, before play")
function gsl_init(render3d_st: mut Render3dState) -> void {
# the evaluate call's structs, made once with DLSS rather than by the first frame it drew
if render3d_st.gsl_res == null { render3d_st.gsl_res = gsl_struct(4 * 112); render3d_st.gsl_tags = gsl_struct(4 * 64); render3d_st.gsl_inputs = bytes(8) }
if render3d_st.gsl_on or Vk.sl_active() == 0 { return }
render3d_st.gsl_feats = gsl_struct(20)
Vk.put_i32(render3d_st.gsl_feats, 0, GSL_DLSS); Vk.put_i32(render3d_st.gsl_feats, 4, GSL_REFLEX); Vk.put_i32(render3d_st.gsl_feats, 8, GSL_PCL)
@ -383,7 +385,7 @@ function gsl_constants(render3d_st: mut Render3dState) -> void {
Vk.put_i32(k, 360, float_bits(1.0)) # mvecScale
Vk.put_i32(k, 364, float_bits(1.0))
gsl_put_v3(k, 376, render3d_st.cam_pos)
let up = floats(3)
let up = render3d_st.gsl_up # made with the state
v3_cross(up, render3d_st.cam_right, render3d_st.cam_fwd)
gsl_put_v3(k, 388, up)
gsl_put_v3(k, 400, render3d_st.cam_right)
@ -397,7 +399,7 @@ function gsl_constants(render3d_st: mut Render3dState) -> void {
# not dilated, not jittered
if render3d_st.gsl_reset { Vk.put_i32(k, 444, 256 * 256 * 256) }
Vk.put_i32(k, 452, float_bits(40.0)) # minRelativeLinearDepthObjectSeparation
free(fix); free(proj); free(v2c); free(c2v); free(cur); free(prev); free(inv_cur); free(c2p); free(p2c); free(ident); free(up)
free(fix); free(proj); free(v2c); free(c2v); free(cur); free(prev); free(inv_cur); free(c2p); free(p2c); free(ident)
}
# make the DLSS targets at this frame's sizes
@ -418,7 +420,6 @@ function gsl_targets(render3d_st: mut Render3dState) -> void {
render3d_st.post_ldr = target_new(render3d_st, gl_width(), gl_height(), post_ldr_fmt(render3d_st), GL_RGBA, GL_UNSIGNED_BYTE, false, GL_LINEAR)
render3d_st.post_ldr_hdr = gpu_hdr_active(render3d_st)
}
if render3d_st.gsl_res == null { render3d_st.gsl_res = gsl_struct(4 * 112); render3d_st.gsl_tags = gsl_struct(4 * 64); render3d_st.gsl_inputs = bytes(8) }
}
# Upscale post_hdr into gsl_out; the colour the rest of post reads (post_hdr's own if it failed).
@ -450,10 +451,14 @@ function gsl_dlss_eval(render3d_st: mut Render3dState) -> int {
# forgetting, because every gvk_draw binds its pipeline, view and set afresh
render3d_st.gsl_eval_ok = r == 0
if r != 0 {
if not render3d_st.gsl_said { print(`r3d: streamline: DLSS evaluate failed ({r}); drawing without it`); render3d_st.gsl_said = true }
if not render3d_st.gsl_said { gsl_say_eval_failed(r); render3d_st.gsl_said = true }
render3d_st.post_color_w = render3d_st.post_w; render3d_st.post_color_h = render3d_st.post_h
return render3d_st.post_hdr.color
}
render3d_st.post_color_w = gl_width(); render3d_st.post_color_h = gl_height()
return render3d_st.gsl_out.color
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function gsl_say_eval_failed(r: int) -> void { print(`r3d: streamline: DLSS evaluate failed ({r}); drawing without it`) }

View file

@ -512,7 +512,7 @@ function terrain_sun_pass(render3d_st: mut Render3dState, w: int, h: int, depth:
if not render3d_st.ter_sun_checked {
render3d_st.ter_sun_checked = true
let st = gpu_fb_status(render3d_st)
if st != GL_FRAMEBUFFER_COMPLETE { print(`r3d: sun-visibility framebuffer incomplete {st}`) }
if st != GL_FRAMEBUFFER_COMPLETE { terrain_say_sun_fbo(st) }
}
gpu_depth_test(render3d_st, true)
gpu_depth_func(render3d_st, GL_LESS)
@ -585,7 +585,7 @@ function terrain_draw(render3d_st: mut Render3dState) -> void {
cdlod_select(render3d_st, CD_LEVELS - 1, 0, 0)
if render3d_st.ter_wire { gpu_wireframe(render3d_st, false) }
gpu_depth_func(render3d_st, GL_LESS)
if render3d_st.r3d_debug and not render3d_st.ter_printed { render3d_st.ter_printed = true; print(`cdlod patches drawn: {render3d_st.cd_draws} (far {render3d_st.cd_far_draws}, near {render3d_st.cd_near_draws}, band {render3d_st.cd_draws - render3d_st.cd_far_draws - render3d_st.cd_near_draws})`) }
if render3d_st.r3d_debug and not render3d_st.ter_printed { render3d_st.ter_printed = true; terrain_say_patches(render3d_st) }
}
# ---- CDLOD --------------------------------------------------------------------------
@ -792,3 +792,9 @@ function terrain_reload(render3d_st: mut Render3dState, dem: string, emin: float
function ter_scratch(render3d_st: mut Render3dState) -> floats {
return render3d_st.ter_scr
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function terrain_say_sun_fbo(st: int) -> void { print(`r3d: sun-visibility framebuffer incomplete {st}`) }
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function terrain_say_patches(render3d_st: Render3dState) -> void { print(`cdlod patches drawn: {render3d_st.cd_draws} (far {render3d_st.cd_far_draws}, near {render3d_st.cd_near_draws}, band {render3d_st.cd_draws - render3d_st.cd_far_draws - render3d_st.cd_near_draws})`) }

View file

@ -104,7 +104,7 @@ function water_reflect_visible(render3d_st: mut Render3dState) -> bool {
var seen = 0
var hidden = 0
for i in 0 .. render3d_st.wb_ncells { if wb_cell_visible(render3d_st, i) { seen += 1; if wb_cell_occluded(render3d_st, i) { hidden += 1 } } }
print(`water reflection test: {render3d_st.wb_ncells} wet rectangles in {render3d_st.wb_nblocks} blocks (built in {render3d_st.wb_build_us} us), {seen} in view, {hidden} of them behind the ground`)
water_say_test(render3d_st, seen, hidden)
}
# A rectangle in the frustum can still be behind the ground or dry where it shows: standing on a shore
# above the lake and looking down at your feet puts water inside the view and none of it on screen.
@ -366,3 +366,7 @@ function water_draw(render3d_st: mut Render3dState, depth_tex: int) -> void {
}
gpu_blend(render3d_st, false)
}
# messages, each built in a function of its own so the path that says it holds no allocation
@alloc_ok("a message, built only when it is said: a failure, a warning or a debug switch")
function water_say_test(render3d_st: Render3dState, seen: int, hidden: int) -> void { print(`water reflection test: {render3d_st.wb_ncells} wet rectangles in {render3d_st.wb_nblocks} blocks (built in {render3d_st.wb_build_us} us), {seen} in view, {hidden} of them behind the ground`) }

View file

@ -695,6 +695,11 @@ function emit_call(e: Node) -> Val {
}
if (name == "len") { return emit_len(e) }
if (name == "push") { return emit_push(e) }
if (name == "keep") and len(e.kids) == 1 { return emit_keep(emit_expr(e.kids[0])) }
if (name == "intern") and len(e.kids) == 1 {
let s = emit_expr(e.kids[0])
return val(emit_bind(`call ptr @lp_intern(ptr {s.code})`), "string")
}
if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through
let a = emit_expr(e.kids[0])
if (llty(a.ty) == "ptr") { return a }

View file

@ -18,6 +18,14 @@ const ES_ESC: int = 1
const ES_HEAP: int = 2
var g_es_flag: []int = new []int
var g_es_why: []Node = new []Node # per class: the statement that made it escape
var g_es_swhy: []Node = new []Node # per store edge: its statement
var g_es_stmt: Node = null # the statement being walked
var g_es_aok: int = 0 # inside a statement under @alloc_ok
var g_es_site_aok: []bool = new []bool # the site is (not reported by the region rule)
var g_es_site_grow: []bool = new []bool # the site is a push's growth (25.5's capacities, not the rule)
var g_es_done: bool = false
var g_es_consumed: []Node = new []Node # text used up where it is made (a + or == operand): freed at once
var g_es_ft: []int = new []int # flow edges
var g_es_ff: []int = new []int
var g_es_st: []int = new []int # store edges: value, target
@ -38,15 +46,20 @@ var g_es_lc: []int = new []int
var g_es_cur: int = -1
var g_es_heap: int = -1 # the one class for globals, states and the unknown
function es_new(flags: int) -> int { push(g_es_flag, flags); return len(g_es_flag) - 1 }
function es_new(flags: int) -> int { push(g_es_flag, flags); push(g_es_why, null); return len(g_es_flag) - 1 }
function es_flow(a: int, b: int) -> void { if a >= 0 and b >= 0 { push(g_es_ff, a); push(g_es_ft, b) } }
function es_store(v: int, t: int) -> void {
if v < 0 { return }
if t < 0 { g_es_flag[v] = g_es_flag[v] | ES_ESC; return }
if t < 0 { es_escape(v); return }
push(g_es_sv, v)
push(g_es_st, t)
push(g_es_swhy, g_es_stmt)
}
function es_escape(v: int) -> void {
if v < 0 { return }
g_es_flag[v] = g_es_flag[v] | ES_ESC
if g_es_why[v] == null { g_es_why[v] = g_es_stmt }
}
function es_escape(v: int) -> void { if v >= 0 { g_es_flag[v] = g_es_flag[v] | ES_ESC } }
function es_local(name: pointer) -> int {
var i = len(g_es_ln) - 1
@ -71,6 +84,8 @@ function es_site(n: Node) -> int {
push(g_es_site, n)
push(g_es_site_cls, c)
push(g_es_site_fn, g_es_cur)
push(g_es_site_aok, g_es_aok > 0)
push(g_es_site_grow, false)
return c
}
@ -100,6 +115,10 @@ function es_val(e: Node) -> int {
if k == E_BIN {
es_val(e.a)
es_val(e.b)
if (e.s == "+") or (e.s == "==") or (e.s == "!=") {
if e.a != null and (e.a.kind == E_BIN or e.a.kind == E_CALL or e.a.kind == E_SLICE) { push(g_es_consumed, e.a) }
if e.b != null and (e.b.kind == E_BIN or e.b.kind == E_CALL or e.b.kind == E_SLICE) { push(g_es_consumed, e.b) }
}
if (e.s == "+") and fr_is_textish(e) { return es_site(e) }
return -1
}
@ -156,11 +175,15 @@ function es_call(e: Node) -> int {
es_args_walk(e)
return es_site(e)
}
if (nm == "keep") or (nm == "intern") {
es_args_walk(e)
return g_es_heap
}
if (nm == "push") and len(e.kids) >= 2 {
let t = es_val(e.kids[0])
es_store(es_val(e.kids[1]), t)
# the grown buffer is the list's own
push(g_es_site, e); push(g_es_site_cls, t); push(g_es_site_fn, g_es_cur)
push(g_es_site, e); push(g_es_site_cls, t); push(g_es_site_fn, g_es_cur); push(g_es_site_aok, g_es_aok > 0); push(g_es_site_grow, true)
return -1
}
if (nm == "words") or (nm == "floats") or (nm == "doubles") or (nm == "buffer") or (nm == "bytes") or (nm == "fixeds") or (nm == "pointers") {
@ -228,7 +251,18 @@ function es_call(e: Node) -> int {
function es_walk(n: Node) -> void {
if n == null { return }
if n.uns == FR_AOK_STMT and n.kind != E_ID {
g_es_aok += 1
es_walk_in(n)
g_es_aok -= 1
return
}
es_walk_in(n)
}
function es_walk_in(n: Node) -> void {
let k = n.kind
if k == S_LET or k == S_ASSIGN or k == S_RETURN or k == S_EXPR or k == S_EMIT { g_es_stmt = n }
if k == S_LET {
let c = es_bind_local(n.s)
es_flow(es_val(n.a), c)
@ -293,14 +327,22 @@ function es_solve() -> void {
let a = g_es_ff[i]
let b = g_es_ft[i]
if (g_es_flag[a] & ES_HEAP) != 0 and (g_es_flag[b] & ES_HEAP) == 0 { g_es_flag[b] = g_es_flag[b] | ES_HEAP; changed = true }
if (g_es_flag[b] & ES_ESC) != 0 and (g_es_flag[a] & ES_ESC) == 0 { g_es_flag[a] = g_es_flag[a] | ES_ESC; changed = true }
if (g_es_flag[b] & ES_ESC) != 0 and (g_es_flag[a] & ES_ESC) == 0 {
g_es_flag[a] = g_es_flag[a] | ES_ESC
g_es_why[a] = g_es_why[b]
changed = true
}
i += 1
}
i = 0
while i < len(g_es_sv) {
let v = g_es_sv[i]
let t = g_es_st[i]
if (g_es_flag[t] & (ES_ESC | ES_HEAP)) != 0 and (g_es_flag[v] & ES_ESC) == 0 { g_es_flag[v] = g_es_flag[v] | ES_ESC; changed = true }
if (g_es_flag[t] & (ES_ESC | ES_HEAP)) != 0 and (g_es_flag[v] & ES_ESC) == 0 {
g_es_flag[v] = g_es_flag[v] | ES_ESC
g_es_why[v] = g_es_swhy[i]
changed = true
}
i += 1
}
}
@ -322,6 +364,8 @@ function es_solve() -> void {
var g_es_local_n: int = 0
var g_es_esc_n: int = 0
function escape_analyse() -> void {
if g_es_done { return }
g_es_done = true
g_es_heap = es_new(ES_ESC | ES_HEAP)
ck_tab_init(g_es_fk, g_es_fv)
var i = 0
@ -400,3 +444,38 @@ function escape_report() -> void {
let tot = `escape: {itoa(g_es_local_n)} local, {itoa(g_es_esc_n)} kept\n`
file_write(file_stderr(), tot, len(tot))
}
# keep(x): x copied onto the heap - a string's bytes, a slice's header and elements, a record's
# fields (shallow) - so a frame's value can become a state's on purpose (25.3's region rule)
function emit_keep(a: Val) -> Val {
if (a.ty == "string") {
let n = emit_bind(`call i64 @strlen(ptr {a.code})`)
let n1 = emit_bind(`add i64 {n}, 1`)
let p = emit_bind(`call ptr @lp_malloc(i64 {n1})`)
emit(` call ptr @memcpy(ptr {p}, ptr {a.code}, i64 {n1})\n`)
return val(p, "string")
}
if is_slice_ty(a.ty) {
let esz = emit_sizeof(llty(slice_elem(a.ty)))
let hsz = emit_sizeof("%LSlice")
let h = emit_bind(`call ptr @lp_malloc(i64 {hsz})`)
let od = emit_bind(`load ptr, ptr {slice_field(a.code, 0)}`)
let ln = emit_bind(`load i32, ptr {slice_field(a.code, 1)}`)
let ln64 = emit_bind(`zext i32 {ln} to i64`)
let bytes = emit_bind(`mul i64 {ln64}, {esz}`)
let b1 = emit_bind(`add i64 {bytes}, 1`)
let d = emit_bind(`call ptr @lp_malloc(i64 {b1})`)
emit(` call ptr @memcpy(ptr {d}, ptr {od}, i64 {bytes})\n`)
emit(` store ptr {d}, ptr {slice_field(h, 0)}\n`)
emit(` store i32 {ln}, ptr {slice_field(h, 1)}\n`)
emit(` store i32 {ln}, ptr {slice_field(h, 2)}\n`)
return val(h, a.ty)
}
if layout_node(a.ty) != null {
let sz = emit_sizeof(layout_ty(a.ty))
let p = emit_bind(`call ptr @lp_malloc(i64 {sz})`)
emit(` call ptr @memcpy(ptr {p}, ptr {a.code}, i64 {sz})\n`)
return val(p, a.ty)
}
return a
}

View file

@ -120,6 +120,7 @@ function fence_flag(a: pointer) -> bool {
if str_starts(a, "--fence-warm=") { g_fence_warm = fence_int(a[13..len(a)]); g_fence_flagged = true; return true }
if str_starts(a, "--fence-census=") { g_fence_census = a[15..len(a)]; g_fence_flagged = true; return true }
if (a == "--arena") { g_arena = true; return true }
if (a == "--arena-strict") { g_arena = true; g_arena_strict = true; return true }
return false
}
@ -152,6 +153,7 @@ function fence_manifest_read(txt: pointer) -> void {
if (len(ws) == 3) and (ws[0] == "fence") and (ws[1] == "warm") { g_fence_warm = fence_int(ws[2]) }
if (len(ws) == 3) and (ws[0] == "fence") and (ws[1] == "census") { g_fence_census = ws[2] }
if (len(ws) == 2) and (ws[0] == "arena") and (ws[1] == "on") { g_arena = true }
if (len(ws) == 2) and (ws[0] == "arena") and (ws[1] == "strict") { g_arena = true; g_arena_strict = true }
i = e + 1
}
}
@ -182,6 +184,7 @@ function fence_table(name: pointer, ty: pointer, vals: []pointer) -> void {
# the runtime, and the tables this program's sites size
function emit_fence_runtime() -> void {
emit_fence_intern()
if g_target_win { emit_fence_win(); return }
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true }
if not g_atexit_declared { emith("declare i32 @atexit(ptr)\n"); g_atexit_declared = true }

View file

@ -1314,3 +1314,67 @@ function emit_fence_win() -> void {
emith(" ret i64 0\n")
emith("}\n")
}
function emit_fence_intern() -> void {
emith("@lp_itab = global [65536 x ptr] zeroinitializer\n")
emith("@lp_icount = global i64 0\n")
emith("define internal ptr @lp_copystr(ptr %s, i64 %n) {\n")
emith("entry:\n")
emith(" %n1 = add i64 %n, 1\n")
emith(" %p = call ptr @lp_malloc(i64 %n1)\n")
emith(" call ptr @memcpy(ptr %p, ptr %s, i64 %n1)\n")
emith(" ret ptr %p\n")
emith("}\n")
emith("define ptr @lp_intern(ptr %s) {\n")
emith("entry:\n")
emith(" %null = icmp eq ptr %s, null\n")
emith(" br i1 %null, label %none, label %hl\n")
emith("none:\n")
emith(" ret ptr null\n")
emith("hl:\n")
emith(" %i = phi i64 [ 0, %entry ], [ %i1, %hb ]\n")
emith(" %h = phi i64 [ -3750763034362895579, %entry ], [ %h2, %hb ]\n")
emith(" %cp = getelementptr i8, ptr %s, i64 %i\n")
emith(" %c = load i8, ptr %cp\n")
emith(" %end = icmp eq i8 %c, 0\n")
emith(" br i1 %end, label %probe, label %hb\n")
emith("hb:\n")
emith(" %cz = zext i8 %c to i64\n")
emith(" %h1 = xor i64 %h, %cz\n")
emith(" %h2 = mul i64 %h1, 1099511628211\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" br label %hl\n")
emith("probe:\n")
emith(" %k = phi i64 [ 0, %hl ], [ %k1, %next ]\n")
emith(" %far = icmp uge i64 %k, 64\n")
emith(" br i1 %far, label %copy_only, label %look\n")
emith("look:\n")
emith(" %slot0 = add i64 %h, %k\n")
emith(" %slot = and i64 %slot0, 65535\n")
emith(" %sp = getelementptr [65536 x ptr], ptr @lp_itab, i64 0, i64 %slot\n")
emith(" %v = load ptr, ptr %sp\n")
emith(" %empty = icmp eq ptr %v, null\n")
emith(" br i1 %empty, label %store, label %cmp\n")
emith("cmp:\n")
emith(" %r = call i32 @strcmp(ptr %v, ptr %s)\n")
emith(" %same = icmp eq i32 %r, 0\n")
emith(" br i1 %same, label %found, label %next\n")
emith("next:\n")
emith(" %k1 = add i64 %k, 1\n")
emith(" br label %probe\n")
emith("found:\n")
emith(" ret ptr %v\n")
emith("store:\n")
emith(" %cnt = load i64, ptr @lp_icount\n")
emith(" %full = icmp uge i64 %cnt, 49152\n")
emith(" br i1 %full, label %copy_only, label %keep\n")
emith("keep:\n")
emith(" %c2 = call ptr @lp_copystr(ptr %s, i64 %i)\n")
emith(" store ptr %c2, ptr %sp\n")
emith(" %cnt1 = add i64 %cnt, 1\n")
emith(" store i64 %cnt1, ptr @lp_icount\n")
emith(" ret ptr %c2\n")
emith("copy_only:\n")
emith(" %c3 = call ptr @lp_copystr(ptr %s, i64 %i)\n")
emith(" ret ptr %c3\n")
emith("}\n")
}

View file

@ -44,6 +44,7 @@ function fr_is_frame_prop_field(prop: pointer, name: pointer) -> bool {
# a generic's instance (kept_push$NetFact) is under its declaration's @alloc_ok
function fr_alloc_ok(name: pointer) -> bool {
if str_starts(name, "ludic_act_") { return true } # the action queue's own kept records, grown to its high-water mark
var base = name
var d = 0
while d < len(name) {
@ -190,4 +191,76 @@ function deps_frame(f: pointer) -> void {
i += 1
}
deps_line(f, `frame_allocs {itoa(total)}`)
deps_keeps(f, seen)
}
var g_arena_strict: bool = false # --arena-strict / `arena strict`: a keep in frame code is an error
# 25.3's region rule: an allocation in frame code that is kept past its frame (stored into a state,
# a queue, a global, an event) is a `fkeep` line - and under strict an error naming the store.
# keep(...) / intern(...) is the copy on purpose; @alloc_ok("why") says it is bounded
function deps_keeps(f: pointer, seen: []bool) -> void {
escape_analyse()
var n = 0
var s = 0
while s < len(g_es_site) {
let c = g_es_site_cls[s]
let fi = g_es_site_fn[s]
if c >= 0 and fi >= 0 and (g_es_flag[c] & ES_ESC) != 0 and not g_es_site_aok[s] and not g_es_site_grow[s] {
let d = g_es_fnode[fi]
let k = dr_index(d.s)
if k >= 0 and seen[k] and not fr_alloc_ok(d.s) and d.file != null and not is_runtime_file(d.file) {
let site = g_es_site[s]
let why = g_es_why[c]
var at = "?"
if why != null and why.file != null { at = `{why.file}:{itoa(why.line)}` }
var kind = fr_alloc_kind(site, false)
if kind == null { kind = "text" }
deps_line(f, `fkeep {kind} {d.file}:{itoa(site.line)} {at} {fr_chain(k)}`)
if g_arena_strict {
let m = `{d.file}:{itoa(site.line)}: error: frame code keeps what it makes: this {kind} is kept at {at} ({fr_chain(k)}) - keep(...) or intern(...) to copy it on purpose, or @alloc_ok("why")\n`
file_write(file_stderr(), m, len(m))
}
n += 1
}
}
s += 1
}
deps_line(f, `frame_keeps {itoa(n)}`)
if g_arena_strict and n > 0 { exit(1) }
deps_births(f, seen)
}
function fr_consumed(n: Node) -> bool {
var i = 0
while i < len(g_es_consumed) { if g_es_consumed[i] == n { return true }; i += 1 }
return false
}
# 25.2's leak-at-birth: an allocation nothing keeps, made where the arena does not take it - outside
# frame code (a boot, a load), in a function spanning frames, or anywhere with the arena off - is
# made and dropped, never given back. `fbirth` lines, and the birth_leaks number
function deps_births(f: pointer, seen: []bool) -> void {
var n = 0
var s = 0
while s < len(g_es_site) {
let c = g_es_site_cls[s]
let fi = g_es_site_fn[s]
let site = g_es_site[s]
if c >= 0 and fi >= 0 and (g_es_flag[c] & ES_ESC) == 0 and not g_es_site_aok[s] and not g_es_site_grow[s] {
let d = g_es_fnode[fi]
let k = dr_index(d.s)
let inframe = k >= 0 and seen[k]
let scratch = g_arena and site.uns == ES_SCRATCH and inframe # before the first frame the arena is not running
if not scratch and d.file != null and not is_runtime_file(d.file) and not fr_alloc_ok(d.s) and not fr_consumed(site) {
var kind = fr_alloc_kind(site, false)
if kind == null { kind = "text" }
var where = "outside-a-frame"
if inframe { where = fr_chain(k) }
deps_line(f, `fbirth {kind} {d.file}:{itoa(site.line)} {d.s} {where}`)
n += 1
}
}
s += 1
}
deps_line(f, `birth_leaks {itoa(n)}`)
}

View file

@ -95,6 +95,11 @@ function ck_walk_args(e: Node) -> void {
}
# the builtins whose type is their name, and push, whose element must fit the slice
function ck_builtin(e: Node, name: pointer) -> pointer {
if (name == "keep") and len(e.kids) == 1 { return ck_expr(e.kids[0]) } # 25.3: a copy on the heap, the same type
if (name == "intern") and len(e.kids) == 1 { # 25.3: one heap string per distinct text
ck_walk_args(e)
return "string"
}
if (name == "string") or (name == "int") or (name == "float") or (name == "fixed") or (name == "long") or (name == "double") {
ck_walk_args(e)
return name

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -232,6 +232,11 @@ entry {
check_program() # L4: the types agree before anything is emitted
if g_es_report { escape_report() } # 25.3: which allocations never outlive their frame
else if g_arena { escape_analyse() } # 25.3: and they allocate from the frame's scratch
if g_arena_strict and (getenv_or("LUDIC_DEPS", "") == "") { # 25.3: the region rule, before anything is emitted
let sink = file_open("/dev/null", "wb")
deps_reach(sink)
file_close(sink)
}
if g_migrate { mg_finish() }
# --check: every check a build makes - the emitter refuses things too (a bind to a function that is
# gone, an unknown name) - and nothing written

View file

@ -17,6 +17,8 @@
# ludic deps --reach N the same, the N that reach the most
# ludic deps --wreach N the N that can come to CHANGE the most states
# ludic deps --allocs every allocation a frame can come to (25.2), with its chain from a root
# ludic deps --keeps what frame code makes and keeps past its frame (25.3), and where
# ludic deps --births what is made, kept by nothing and not the arena's: never given back
#
# dependencies: module pairs (a, b) where a uses something of b, not counting b when b uses no
# other module (a leaf); largest_cycle: the largest set of modules that all reach each other, not
@ -51,6 +53,10 @@ var dp_fn_r: []int = null
var dp_fn_x: []int = null # ... and the states it can come to change
var dp_fallocs: []pointer = new []pointer # 25.2: `falloc <kind> <file:line> <chain>`, what a frame can allocate
var dp_frame_allocs: int = 0
var dp_fkeeps: []pointer = new []pointer # 25.3: `fkeep <kind> <file:line> <kept at> <chain>`
var dp_frame_keeps: int = 0
var dp_fbirths: []pointer = new []pointer # 25.2: `fbirth <kind> <file:line> <fn> <where>`
var dp_birth_leaks: int = 0
var dp_wreach_n: int = 0
var dp_wreach_at: pointer = ""
@ -90,6 +96,8 @@ function dp_load(path: pointer) -> bool {
dp_fn_at = new []pointer; dp_fn_w = new []int; dp_fn_r = new []int; dp_fn_x = new []int
dp_wreach_n = 0; dp_wreach_at = ""
dp_fallocs = new []pointer; dp_frame_allocs = 0
dp_fkeeps = new []pointer; dp_frame_keeps = 0
dp_fbirths = new []pointer; dp_birth_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) {
@ -113,6 +121,10 @@ function dp_load(path: pointer) -> bool {
if len(w) >= 6 and w[0] == "alias" { push(dp_aliases, lines[i]) }
if len(w) >= 4 and w[0] == "falloc" { push(dp_fallocs, lines[i]) }
if len(w) == 2 and w[0] == "frame_allocs" { dp_frame_allocs = s_to_int(w[1]) }
if len(w) >= 5 and w[0] == "fkeep" { push(dp_fkeeps, lines[i]) }
if len(w) == 2 and w[0] == "frame_keeps" { dp_frame_keeps = s_to_int(w[1]) }
if len(w) >= 4 and w[0] == "fbirth" { push(dp_fbirths, lines[i]) }
if len(w) == 2 and w[0] == "birth_leaks" { dp_birth_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]})`
@ -312,7 +324,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"]
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"]
var mods = 0
for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } }
var deps = 0
@ -334,7 +346,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_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]
}
# ---- the command --------------------------------------------------------------
@ -489,6 +501,8 @@ function cmd_deps() -> int {
else if a == "--graph" { mode = "graph" }
else if a == "--writes" { mode = "writes" }
else if a == "--allocs" { mode = "allocs" }
else if a == "--keeps" { mode = "keeps" }
else if a == "--births" { mode = "births" }
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))
@ -538,6 +552,24 @@ function cmd_deps() -> int {
print(`frame_allocs: {string(dp_frame_allocs)}`)
return 0
}
if mode == "keeps" { # 25.3: what frame code makes and keeps, and where it is kept
for a in 0 .. len(dp_fkeeps) {
let w = dp_words(dp_fkeeps[a])
print(`frame keep: {w[2]} {w[1]} kept at {w[3]} {w[4]}`)
}
print(`frame_keeps: {string(dp_frame_keeps)}`)
return 0
}
if mode == "births" { # 25.2: made, kept by nothing, and on the heap: never given back
for a in 0 .. len(dp_fbirths) {
let w = dp_words(dp_fbirths[a])
var where = ""
if len(w) >= 5 { where = w[4] }
print(`leak at birth: {w[2]} {w[1]} in {w[3]} {where}`)
}
print(`birth_leaks: {string(dp_birth_leaks)}`)
return 0
}
if mode == "widest" { dp_top(s_to_int(arg2), 0); return 0 }
if mode == "reach" { dp_top(s_to_int(arg2), 1); return 0 }
if mode == "wreach" { dp_top(s_to_int(arg2), 2); return 0 }