Merge lang/foundations 69d1db0 into lang/ecs
This commit is contained in:
commit
23d204b9fc
22 changed files with 83199 additions and 77641 deletions
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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})`) }
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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}`) }
|
||||
|
|
|
|||
|
|
@ -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}`) }
|
||||
|
|
|
|||
|
|
@ -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`) }
|
||||
|
|
|
|||
|
|
@ -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`) }
|
||||
|
|
|
|||
|
|
@ -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})`) }
|
||||
|
|
|
|||
|
|
@ -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`) }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
80217
selfhost/ludicc.seed.ll
80217
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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue