Every allocation the compiler emits goes through @lp_malloc/@lp_calloc/@lp_realloc/@lp_free, and a Ludic-level one first stores its site (function, file, line, kind) in @lp_site. Off, that is one load and a predictable branch (30 M allocations: 0.87-0.91 s against 0.87-0.90 s on leaks2). On (the default in a headless build, and windowed under R3D_DEV), tracking starts at the first frame on its own and judging once R3D_ALLOC_WARM frames in a row kept nothing (600) or R3D_ALLOC_WARM_MAX after (re)start; Mem.play()/Mem.rewarm() sends a load back to its warm-up. A judged frame that ends holding more than it began with is reported by site with its callers (the unwinder, taken only once judging) and fails the run with exit 86 (R3D_ALLOC_FENCE=off|count|warn|fail). R3D_ALLOC_CENSUS writes the totals and top sites at exit. The build's defaults are --fence=, --fence-warm=, --fence-census= or a fence line in the program's package.ludic; the environment overrides them. The runtime is IR (emit_fence_ir.ludic, generated from a template); tracking is a side table in one calloc'd region, so no block carries a header and pointers crossing to natives stay safe. Examples alloc_fence, alloc_fence_leak and alloc_fence_auto with cases in ludic-dev test; reseeded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
119 lines
9.3 KiB
Text
119 lines
9.3 KiB
Text
# emit_intrin2.ludic — the rest of the low-level intrinsics: raw memory, fixed
|
|
# memory access, pointer arithmetic, stdio, time, and the windowing hooks.
|
|
# Matches compiler/back/ir_intrin.c. Reached from emit_intrinsic's fall-through.
|
|
|
|
var g_windowed: bool = false # headless by default (games read stdin / dump PPM)
|
|
var g_target_win: bool = false # a Windows target: the header emits emit_win_prelude and the libSystem declares are skipped
|
|
|
|
# the IR that loads stdout (fd 1) or stderr (fd 2) as a FILE*. macOS names them as
|
|
# globals; the UCRT hands them out through a function (see emit_win.ludic).
|
|
function stdstream_rhs(fd: int) -> pointer {
|
|
if g_target_win { return `call ptr @lp_win_stdio(i32 {itoa(fd)})` }
|
|
if fd == 2 { return "load ptr, ptr @__stderrp" }
|
|
return "load ptr, ptr @__stdoutp"
|
|
}
|
|
|
|
function is_intrinsic2(name: pointer) -> bool {
|
|
if (name == "free") or (name == "fill") { return true }
|
|
if (name == "offset") or (name == "read_char") { return true }
|
|
if (name == "as_fixed") or (name == "as_int") { return true }
|
|
if (name == "is_windowed") or (name == "game_title") { return true }
|
|
if (name == "win_open") or (name == "win_poll") or (name == "win_present") { return true }
|
|
if (name == "win_running") or (name == "win_close") { return true }
|
|
if (name == "win_held") or (name == "win_mouse") { return true } # #50 device layer
|
|
if (name == "win_text") { return true } # typed characters
|
|
if (name == "win_key_char") { return true } # Input.key_label
|
|
if (name == "win_pad") or (name == "win_touch") { return true } # #51 gamepad / touch
|
|
if (name == "win_cursor_mode") { return true } # #89 cursor capture
|
|
if (name == "snd_load") or (name == "snd_play") or (name == "snd_stop") { return true } # #22 audio
|
|
if (name == "snd_playing") or (name == "snd_set_volume") or (name == "snd_set_rate") or (name == "snd_set_pan") { return true }
|
|
if (name == "hs_req_new") or (name == "hs_req_header") or (name == "hs_req_body") { return true } # #6 HTTP
|
|
if (name == "hs_send") or (name == "hs_done") or (name == "hs_status") or (name == "hs_body") { return true }
|
|
if (name == "hs_blen") or (name == "hs_header") or (name == "hs_free") { return true }
|
|
return false
|
|
}
|
|
|
|
function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
|
if (name == "free") {
|
|
let fv = emit_expr(e.kids[0])
|
|
# a slice is its elements and its header: both go (L7)
|
|
if is_slice_ty(fv.ty) {
|
|
let fd = emit_bind(`load ptr, ptr {slice_field(fv.code, 0)}`)
|
|
emit(` call void @lp_free(ptr {fd})\n`)
|
|
}
|
|
emit(` call void @lp_free(ptr {fv.code})\n`)
|
|
return val("0", "void")
|
|
}
|
|
if (name == "fill") {
|
|
let p = raw_expr(e.kids[0]).code; let v = arg_code(e, 1); let n = arg_code(e, 2)
|
|
let w = emit_bind(`zext i32 {n} to i64`)
|
|
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if (name == "offset") {
|
|
let p = raw_expr(e.kids[0]).code; let n = arg_code(e, 1)
|
|
let g = nreg()
|
|
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
|
|
return val(g, "pointer")
|
|
}
|
|
if (name == "read_char") { return val(emit_bind("call i32 @getchar()"), "int") }
|
|
if (name == "as_fixed") { let a = emit_expr(e.kids[0]); return val(a.code, "fixed") }
|
|
if (name == "as_int") { let a = emit_expr(e.kids[0]); return val(a.code, "int") }
|
|
if (name == "is_windowed") { if g_windowed { return val("1", "bool") }; return val("0", "bool") }
|
|
if (name == "game_title") { return val("@.gametitle", "string") }
|
|
# windowing hooks — declared external; only reached on the is_windowed() branch
|
|
if (name == "win_poll") { return val(emit_bind("call i32 @win_poll()"), "int") }
|
|
if (name == "win_running") { return val(emit_bind("call i32 @win_running()"), "bool") }
|
|
if (name == "win_open") {
|
|
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); let d = arg_code(e, 3)
|
|
emit(" call void @win_open(i32 "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(", ptr "); emit(d); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if (name == "win_present") {
|
|
let a = arg_buf(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2)
|
|
emit(" call void @win_present(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(")\n")
|
|
return val("0", "void")
|
|
}
|
|
if (name == "win_close") { emit(" call void @win_close()\n"); return val("0", "void") }
|
|
# #50 — fill a caller buffer with the platform device state (windowed only,
|
|
# DCE'd headless). win_held: an 8-word held-key bitset; win_mouse: [x, y,
|
|
# button-mask, wheel-delta].
|
|
if (name == "win_held") { let a = arg_buf(e, 0); emit(" call void @win_held(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
if (name == "win_mouse") { let a = arg_buf(e, 0); emit(" call void @win_mouse(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
# what the active keyboard layout types on a held-set key position (windowed only,
|
|
# DCE'd headless): a code point, 0 for none.
|
|
if (name == "win_key_char") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @win_key_char(i32 {a})`), "int") }
|
|
# what the player TYPED since the last call - UTF-16 code units into the caller's buffer, one per
|
|
# word, returning how many. The layout, the modifiers and any dead key are already applied, which
|
|
# is why a name field must read this rather than build text out of key codes.
|
|
if (name == "win_text") { let a = arg_buf(e, 0); let b = arg_code(e, 1); return val(emit_bind(`call i32 @win_text(ptr {a}, i32 {b})`), "int") }
|
|
# #51 — win_pad: 6 words/pad [connected, mask, lx, ly, rx, ry]; win_touch: 3
|
|
# words/point [active, x, y]. Both windowed-only, DCE'd headless.
|
|
if (name == "win_pad") { let a = arg_buf(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
if (name == "win_touch") { let a = arg_buf(e, 0); emit(" call void @win_touch(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
# #89 — set the OS cursor capture mode (windowed only; DCE'd headless).
|
|
if (name == "win_cursor_mode") { let a = arg_code(e, 0); emit(" call void @win_cursor_mode(i32 "); emit(a); emit(")\n"); return val("0", "void") }
|
|
# #22 audio backend — AVAudioPlayer via audio.ll; windowed-only, DCE'd headless.
|
|
if (name == "snd_load") { let a = arg_code(e, 0); return val(emit_bind(`call ptr @snd_load(ptr {a})`), "pointer") }
|
|
if (name == "snd_play") {
|
|
let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); let d = arg_code(e, 3)
|
|
emit(" call void @snd_play(ptr "); emit(a); emit(", i32 "); emit(b); emit(", i32 "); emit(c); emit(", i32 "); emit(d); emit(")\n"); return val("0", "void")
|
|
}
|
|
if (name == "snd_stop") { let a = arg_code(e, 0); emit(" call void @snd_stop(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
|
if (name == "snd_playing") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @snd_playing(ptr {a})`), "int") }
|
|
if (name == "snd_set_volume") { let a = arg_code(e, 0); let b = arg_code(e, 1); emit(" call void @snd_set_volume(ptr "); emit(a); emit(", i32 "); emit(b); emit(")\n"); return val("0", "void") }
|
|
if (name == "snd_set_rate") { let a = arg_code(e, 0); let b = arg_code(e, 1); emit(" call void @snd_set_rate(ptr "); emit(a); emit(", i32 "); emit(b); emit(")\n"); return val("0", "void") }
|
|
if (name == "snd_set_pan") { let a = arg_code(e, 0); let b = arg_code(e, 1); emit(" call void @snd_set_pan(ptr "); emit(a); emit(", i32 "); emit(b); emit(")\n"); return val("0", "void") }
|
|
# #6 HTTP transport — NSURLConnection on a background thread (http.ll).
|
|
if (name == "hs_req_new") { let a = arg_code(e, 0); let b = arg_code(e, 1); return val(emit_bind(`call ptr @hs_req_new(ptr {a}, ptr {b})`), "pointer") }
|
|
if (name == "hs_req_header") { let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); emit(" call void @hs_req_header(ptr "); emit(a); emit(", ptr "); emit(b); emit(", ptr "); emit(c); emit(")\n"); return val("0", "void") }
|
|
if (name == "hs_req_body") { let a = arg_code(e, 0); let b = arg_code(e, 1); let c = arg_code(e, 2); emit(" call void @hs_req_body(ptr "); emit(a); emit(", ptr "); emit(b); emit(", i32 "); emit(c); emit(")\n"); return val("0", "void") }
|
|
if (name == "hs_send") { let a = arg_code(e, 0); let b = arg_code(e, 1); emit(" call void @hs_send(i32 "); emit(a); emit(", ptr "); emit(b); emit(")\n"); return val("0", "void") }
|
|
if (name == "hs_done") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @hs_done(i32 {a})`), "int") }
|
|
if (name == "hs_status") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @hs_status(i32 {a})`), "int") }
|
|
if (name == "hs_body") { let a = arg_code(e, 0); return val(emit_bind(`call ptr @hs_body(i32 {a})`), "pointer") }
|
|
if (name == "hs_blen") { let a = arg_code(e, 0); return val(emit_bind(`call i32 @hs_blen(i32 {a})`), "int") }
|
|
if (name == "hs_header") { let a = arg_code(e, 0); let b = arg_code(e, 1); return val(emit_bind(`call ptr @hs_header(i32 {a}, ptr {b})`), "pointer") }
|
|
if (name == "hs_free") { let a = arg_code(e, 0); emit(" call void @hs_free(i32 "); emit(a); emit(")\n"); return val("0", "void") }
|
|
return val("0", "void")
|
|
}
|