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>
356 lines
17 KiB
Text
356 lines
17 KiB
Text
# emit_head.ludic — string constants and the module header (libc declarations,
|
|
# the slice header type, struct layouts, globals, argv, format strings).
|
|
|
|
function hexdig(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } # 0-9 A-F
|
|
|
|
# emit `@.strN = ... c"escaped\00"` and return its name; % and non-print -> \XX
|
|
function emit_str_const(s: pointer) -> pointer {
|
|
let name = `@.str{itoa(ll_str)}`
|
|
ll_str += 1
|
|
let n = len(s)
|
|
emith(name); emith(" = private unnamed_addr constant [")
|
|
emith(itoa(n + 1)); emith(" x i8] c\"")
|
|
emit_escaped(s)
|
|
emith("\\00\"\n")
|
|
return name
|
|
}
|
|
|
|
# the bytes of s as the body of an IR c"..." string: " \ and non-print -> \XX
|
|
function emit_escaped(s: pointer) -> void {
|
|
let n = len(s)
|
|
var i = 0
|
|
while i < n {
|
|
let c = s[i]
|
|
if c == '"' or c == CH_BACKSLASH or c < ' ' or c > '~' {
|
|
buf_putc(head, CH_BACKSLASH) # \XX hex escape
|
|
buf_putc(head, hexdig(c / 16))
|
|
buf_putc(head, hexdig(c % 16))
|
|
} else { buf_putc(head, c) }
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
# an int literal's LLVM constant: its value, or a long literal's own digits
|
|
function int_lit_code(e: Node) -> pointer {
|
|
if e.s != null { return e.s }
|
|
return itoa(e.ival)
|
|
}
|
|
# the constant initializer for a global var: a literal, or 0/null
|
|
function global_init(d: Node) -> pointer {
|
|
if (d.a == null) or is_fp(d.ty) { return zero_of(llty(d.ty)) } # a float's value is set by L_init_globals
|
|
let e = d.a
|
|
if e.kind == E_INT { return int_lit_code(e) }
|
|
if e.kind == E_FLOAT or e.kind == E_BOOL { return itoa(e.ival) }
|
|
if e.kind == E_UN and (e.s == ("-")) and e.a.kind == E_INT { return (("-") + int_lit_code(e.a)) }
|
|
if e.kind == E_MEMBER and e.a.kind == E_ID { # `Enum.Variant` is a compile-time int
|
|
let ord = enum_ordinal(e.a.s, e.s)
|
|
if ord >= 0 { return itoa(ord) }
|
|
}
|
|
if (llty(d.ty) == "ptr") { return "null" }
|
|
return "0"
|
|
}
|
|
# does a global's initializer need code at startup (anything global_init cannot fold)?
|
|
function global_needs_init_code(d: Node) -> bool {
|
|
if (d.a == null) { return false }
|
|
if is_fp(d.ty) { return true }
|
|
let e = d.a
|
|
if e.kind == E_INT or e.kind == E_FLOAT or e.kind == E_BOOL or e.kind == E_NULL { return false }
|
|
if e.kind == E_UN and (e.s == ("-")) and e.a.kind == E_INT { return false }
|
|
if e.kind == E_MEMBER and e.a.kind == E_ID { if enum_ordinal(e.a.s, e.s) >= 0 { return false } }
|
|
return true
|
|
}
|
|
|
|
# @L_init_globals(): evaluate every global initializer global_init could not fold
|
|
# (`var run: Progress = new Progress`, `var speed: int = BASE * 2`, a call), in
|
|
# declaration order, once at startup — after the runtime boots, before Start.
|
|
function emit_global_init_fn() -> void {
|
|
emit_init_fn("L_init_runtime", true)
|
|
emit_init_fn("L_init_globals", false)
|
|
}
|
|
# 0.S: the runtime's states are made first, before it boots (rt_init reads them); the rest after
|
|
function is_runtime_state_var(d: Node) -> bool { return d.uns == 1 and is_state_ty(d.ty) and is_runtime_file(d.file) }
|
|
function emit_init_fn(name: pointer, runtime: bool) -> void {
|
|
fence_enter(name)
|
|
ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0
|
|
ret_ty = "void"
|
|
let fbody = buf_new()
|
|
falloc = buf_new()
|
|
let saved = code
|
|
code = fbody
|
|
var i = 0
|
|
while i < len(prog) {
|
|
let d = prog[i]
|
|
if d.kind == N_VAR and global_needs_init_code(d) and is_runtime_state_var(d) == runtime {
|
|
# an initializer is the global's own file's code: its errors, and what its module
|
|
# may see (L3), are that file's - not whichever statement was lowered last
|
|
g_err_file = d.file
|
|
g_err_line = d.line
|
|
# a port's bind is written in the app and checked there (frontend/ports.ludic)
|
|
if is_port_var(d) {
|
|
g_err_file = d.a.file
|
|
g_err_line = d.a.line
|
|
g_vis_off = true
|
|
}
|
|
let v = emit_expr(d.a)
|
|
g_vis_off = false
|
|
let cv = coerce_code(v, d.ty) # any conversion is its own line, before the store
|
|
emit(" store "); emit(llty(d.ty)); emit(" "); emit(cv); emit(", ptr @g_"); emit(d.s); emit("\n")
|
|
}
|
|
i += 1
|
|
}
|
|
emit(" ret void\n")
|
|
code = saved
|
|
emit(`define void @{name}() {{\nentry:\n`)
|
|
emit(buf_str(falloc))
|
|
emit(buf_str(fbody))
|
|
emit("}\n\n")
|
|
}
|
|
|
|
function emit_header() -> void {
|
|
emith("; Ludic (self-hosted) -> LLVM IR\n")
|
|
emith("declare i32 @printf(ptr, ...)\n")
|
|
emith("declare ptr @malloc(i64)\n")
|
|
emith("declare ptr @calloc(i64, i64)\n")
|
|
emith("declare ptr @realloc(ptr, i64)\n")
|
|
emith("declare void @free(ptr)\n")
|
|
emith("declare i64 @fread(ptr, i64, i64, ptr)\n")
|
|
emith("declare i64 @fwrite(ptr, i64, i64, ptr)\n")
|
|
emith("declare i32 @fclose(ptr)\n")
|
|
# on Windows these four are defined by emit_win_prelude, below
|
|
if not g_target_win {
|
|
emith("declare ptr @fopen(ptr, ptr)\n")
|
|
emith("declare i32 @fseek(ptr, i64, i32)\n")
|
|
emith("declare i64 @ftell(ptr)\n")
|
|
emith("declare i64 @time(ptr)\n")
|
|
}
|
|
emith("declare void @exit(i32)\n")
|
|
emith("declare i32 @system(ptr)\n")
|
|
emith("declare ptr @getenv(ptr)\n")
|
|
emith("declare ptr @memcpy(ptr, ptr, i64)\n")
|
|
emith("declare ptr @memset(ptr, i32, i64)\n")
|
|
emith("declare ptr @memmove(ptr, ptr, i64)\n")
|
|
emith("declare i64 @strlen(ptr)\n")
|
|
emith("declare i32 @strcmp(ptr, ptr)\n")
|
|
emith("declare i32 @strncmp(ptr, ptr, i64)\n")
|
|
emith("declare ptr @strstr(ptr, ptr)\n")
|
|
emith("declare i32 @atoi(ptr)\n")
|
|
emith("declare i32 @getchar()\n")
|
|
emith("declare i32 @putchar(i32)\n")
|
|
emith("declare void @win_open(i32, i32, i32, ptr)\n")
|
|
emith("declare i32 @win_poll()\n")
|
|
emith("declare void @win_present(ptr, i32, i32)\n")
|
|
emith("declare i32 @win_running()\n")
|
|
emith("declare void @win_close()\n")
|
|
emith("declare void @win_held(ptr)\n")
|
|
emith("declare i32 @win_key_char(i32)\n")
|
|
emith("declare i32 @win_text(ptr, i32)\n")
|
|
emith("declare void @win_mouse(ptr)\n")
|
|
emith("declare void @win_pad(ptr)\n")
|
|
emith("declare void @win_touch(ptr)\n")
|
|
emith("declare void @win_cursor_mode(i32)\n")
|
|
emith("declare ptr @snd_load(ptr)\n") # #22 audio backend (audio.ll)
|
|
emith("declare void @snd_play(ptr, i32, i32, i32)\n")
|
|
emith("declare void @snd_stop(ptr)\n")
|
|
emith("declare i32 @snd_playing(ptr)\n")
|
|
emith("declare void @snd_set_volume(ptr, i32)\n")
|
|
emith("declare void @snd_set_rate(ptr, i32)\n")
|
|
emith("declare void @snd_set_pan(ptr, i32)\n")
|
|
emith("declare ptr @hs_req_new(ptr, ptr)\n") # #6 HTTP transport (http.ll)
|
|
emith("declare void @hs_req_header(ptr, ptr, ptr)\n")
|
|
emith("declare void @hs_req_body(ptr, ptr, i32)\n")
|
|
emith("declare void @hs_send(i32, ptr)\n")
|
|
emith("declare i32 @hs_done(i32)\n")
|
|
emith("declare i32 @hs_status(i32)\n")
|
|
emith("declare ptr @hs_body(i32)\n")
|
|
emith("declare i32 @hs_blen(i32)\n")
|
|
emith("declare ptr @hs_header(i32, ptr)\n")
|
|
emith("declare void @hs_free(i32)\n")
|
|
if g_target_win { emit_win_prelude() } # the UCRT/Win32 definitions of the POSIX names above
|
|
else {
|
|
emith("@__stderrp = external global ptr\n")
|
|
emith("@__stdoutp = external global ptr\n")
|
|
}
|
|
emith("@.fmt_int = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\"\n")
|
|
emith("@.fmt_long = private unnamed_addr constant [6 x i8] c\"%lld\\0A\\00\"\n")
|
|
emith("@.fmt_line = private unnamed_addr constant [4 x i8] c\"%s\\0A\\00\"\n")
|
|
emith("@L_argc = internal global i32 0\n")
|
|
emith("@L_argv = internal global ptr null\n")
|
|
emith("@L_clock = internal global i32 0\n") # Clock.* — the game-controlled simulated clock
|
|
emith("@.gametitle = private unnamed_addr constant [")
|
|
emith(itoa(len(g_game_name) + 1)); emith(" x i8] c\""); emit_escaped(g_game_name); emith("\\00\"\n")
|
|
emith("%LSlice = type { ptr, i32, i32 }\n")
|
|
# property layouts — a %Cmp_ record of named fields, emitted here so `new`
|
|
# works whether or not the program runs the ECS. The per-entity @S_/@H_ arrays
|
|
# are separate (emit_ecs_storage), emitted only for a program that runs the ECS.
|
|
var i = 0
|
|
while i < len(prog) {
|
|
let d = prog[i]
|
|
if d.kind == N_COMP {
|
|
var j = 0 # the same record twice would silently use the first
|
|
while j < i {
|
|
if prog[j].kind == N_COMP and (prog[j].s == d.s) {
|
|
if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`property {d.s} is also a property of the engine runtime; choose another name`) }
|
|
perr(`property {d.s} is declared twice`)
|
|
}
|
|
j += 1
|
|
}
|
|
emith(layout_ty(d.s)); emith(" = type { ")
|
|
if len(d.kids) == 0 { emith("i32") }
|
|
var f = 0
|
|
while f < len(d.kids) {
|
|
if f > 0 { emith(", ") }
|
|
emith(llty(d.kids[f].ty))
|
|
f += 1
|
|
}
|
|
emith(" }\n")
|
|
}
|
|
i += 1
|
|
}
|
|
# globals (vars) — aggregates/pointers default to null, scalars to 0
|
|
i = 0
|
|
while i < len(prog) {
|
|
let d = prog[i]
|
|
if d.kind == N_VAR {
|
|
var j = 0 # the same name twice would be one LLVM global
|
|
while j < i {
|
|
if prog[j].kind == N_VAR and (prog[j].s == d.s) {
|
|
if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`variable {d.s} is also a variable of the engine runtime; choose another name`) }
|
|
perr(`variable {d.s} is declared twice`)
|
|
}
|
|
j += 1
|
|
}
|
|
emith("@g_"); emith(d.s); emith(" = internal global ")
|
|
emith(llty(d.ty)); emith(" ")
|
|
emith(global_init(d))
|
|
emith("\n")
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
# One `declare <ret> @<sym>(<argtys>)` per `extern fn`, so the linker resolves the
|
|
# call to the bound symbol. Emitted after the header; a program with no `extern fn`
|
|
# emits nothing here, so un-networked builds stay byte-identical.
|
|
function emit_extern_decls() -> void {
|
|
var i = 0
|
|
while i < len(prog) {
|
|
let d = prog[i]
|
|
if d.kind == N_EXTERN {
|
|
emith("declare "); emith(llty(d.ty)); emith(" @"); emith(d.a.s); emith("(")
|
|
var f = 0
|
|
while f < len(d.kids) {
|
|
if f > 0 { emith(", ") }
|
|
emith(llty(d.kids[f].ty))
|
|
f += 1
|
|
}
|
|
emith(")\n")
|
|
}
|
|
i += 1
|
|
}
|
|
}
|
|
|
|
# The string runtime, emitted (once) into any program that uses `+`/`==`/`!=`
|
|
# on strings. Hand-written IR over NUL-terminated byte buffers: str_eq walks both
|
|
# until a mismatch or a shared terminator; str_concat measures both, mallocs
|
|
# len+len+1, copies each half, and NUL-terminates. @malloc is always declared.
|
|
function emit_str_prelude() -> void {
|
|
emith("define i32 @lp_str_eq(ptr %a, ptr %b) {\n")
|
|
emith("entry:\n br label %loop\n")
|
|
emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n")
|
|
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %i\n")
|
|
emith(" %pb = getelementptr inbounds i8, ptr %b, i32 %i\n")
|
|
emith(" %ca = load i8, ptr %pa\n %cb = load i8, ptr %pb\n")
|
|
emith(" %df = icmp ne i8 %ca, %cb\n br i1 %df, label %ret0, label %chk\n")
|
|
emith("chk:\n %zt = icmp eq i8 %ca, 0\n br i1 %zt, label %ret1, label %cont\n")
|
|
emith("cont:\n %i1 = add i32 %i, 1\n br label %loop\n")
|
|
emith("ret1:\n ret i32 1\nret0:\n ret i32 0\n}\n")
|
|
|
|
emith("define ptr @lp_str_concat(ptr %a, ptr %b) {\n")
|
|
emith("entry:\n br label %al\n")
|
|
emith("al:\n %ia = phi i32 [ 0, %entry ], [ %ia1, %alb ]\n")
|
|
emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %ia\n %cca = load i8, ptr %pa\n")
|
|
emith(" %za = icmp eq i8 %cca, 0\n br i1 %za, label %bl0, label %alb\n")
|
|
emith("alb:\n %ia1 = add i32 %ia, 1\n br label %al\n")
|
|
emith("bl0:\n br label %bl\n")
|
|
emith("bl:\n %ib = phi i32 [ 0, %bl0 ], [ %ib1, %blb ]\n")
|
|
emith(" %pb = getelementptr inbounds i8, ptr %b, i32 %ib\n %ccb = load i8, ptr %pb\n")
|
|
emith(" %zb = icmp eq i8 %ccb, 0\n br i1 %zb, label %alloc, label %blb\n")
|
|
emith("blb:\n %ib1 = add i32 %ib, 1\n br label %bl\n")
|
|
emith("alloc:\n %sum = add i32 %ia, %ib\n %sz = add i32 %sum, 1\n")
|
|
emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @lp_malloc(i64 %sz64)\n br label %c1\n")
|
|
emith("c1:\n %i = phi i32 [ 0, %alloc ], [ %i1, %c1b ]\n")
|
|
emith(" %d1 = icmp slt i32 %i, %ia\n br i1 %d1, label %c1b, label %c2i\n")
|
|
emith("c1b:\n %s1 = getelementptr inbounds i8, ptr %a, i32 %i\n %v1 = load i8, ptr %s1\n")
|
|
emith(" %o1 = getelementptr inbounds i8, ptr %out, i32 %i\n store i8 %v1, ptr %o1\n")
|
|
emith(" %i1 = add i32 %i, 1\n br label %c1\n")
|
|
emith("c2i:\n br label %c2\n")
|
|
emith("c2:\n %j = phi i32 [ 0, %c2i ], [ %j1, %c2b ]\n")
|
|
emith(" %d2 = icmp slt i32 %j, %ib\n br i1 %d2, label %c2b, label %fin\n")
|
|
emith("c2b:\n %s2 = getelementptr inbounds i8, ptr %b, i32 %j\n %v2 = load i8, ptr %s2\n")
|
|
emith(" %oj = add i32 %ia, %j\n %o2 = getelementptr inbounds i8, ptr %out, i32 %oj\n store i8 %v2, ptr %o2\n")
|
|
emith(" %j1 = add i32 %j, 1\n br label %c2\n")
|
|
emith("fin:\n %pe = getelementptr inbounds i8, ptr %out, i32 %sum\n store i8 0, ptr %pe\n ret ptr %out\n}\n")
|
|
}
|
|
|
|
# int -> decimal string, emitted (once) into any program that uses string(int)
|
|
# (string interpolation of a number). Writes digits from the end of a 24-byte
|
|
# buffer, prepends '-' for negatives, and moves them to the buffer's start.
|
|
function emit_int_str() -> void {
|
|
emith("define ptr @lp_int_str(i32 %n0) {\n")
|
|
emith("entry:\n %buf = call ptr @lp_malloc(i64 24)\n")
|
|
emith(" %isneg = icmp slt i32 %n0, 0\n %neg = sub i32 0, %n0\n")
|
|
emith(" %n = select i1 %isneg, i32 %neg, i32 %n0\n")
|
|
emith(" %term = getelementptr inbounds i8, ptr %buf, i32 23\n store i8 0, ptr %term\n")
|
|
emith(" %iszero = icmp eq i32 %n0, 0\n br i1 %iszero, label %zc, label %dl\n")
|
|
emith("zc:\n store i8 48, ptr %buf\n %z1 = getelementptr inbounds i8, ptr %buf, i32 1\n store i8 0, ptr %z1\n ret ptr %buf\n")
|
|
emith("dl:\n br label %dloop\n")
|
|
emith("dloop:\n %pos = phi i32 [ 22, %dl ], [ %pos2, %dbody ]\n %cur = phi i32 [ %n, %dl ], [ %cur2, %dbody ]\n")
|
|
emith(" %done = icmp eq i32 %cur, 0\n br i1 %done, label %sign, label %dbody\n")
|
|
emith("dbody:\n %d = urem i32 %cur, 10\n %ch = add i32 %d, 48\n %ch8 = trunc i32 %ch to i8\n")
|
|
emith(" %pp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 %ch8, ptr %pp\n")
|
|
emith(" %cur2 = udiv i32 %cur, 10\n %pos2 = sub i32 %pos, 1\n br label %dloop\n")
|
|
emith("sign:\n br i1 %isneg, label %addneg, label %fin\n")
|
|
emith("addneg:\n %sp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 45, ptr %sp\n %posn = sub i32 %pos, 1\n br label %fin\n")
|
|
emith("fin:\n %fpos = phi i32 [ %pos, %sign ], [ %posn, %addneg ]\n")
|
|
emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n")
|
|
# the digits to the buffer's start, so what is returned is what was malloc'd and can be freed
|
|
emith(" %cnt = sub i32 24, %rpos\n %cnt64 = sext i32 %cnt to i64\n call ptr @memmove(ptr %buf, ptr %res, i64 %cnt64)\n ret ptr %buf\n}\n")
|
|
}
|
|
|
|
# the i64 twin of fn_int_str: a signed 64-bit integer -> decimal text. Emitted
|
|
# once per program that stringifies a `long` (g_uses_longstr). A 64-bit value is
|
|
# at most 20 digits plus sign and NUL, so the 24-byte scratch buffer still fits.
|
|
function emit_long_str() -> void {
|
|
emith("define ptr @lp_long_str(i64 %n0) {\n")
|
|
emith("entry:\n %buf = call ptr @lp_malloc(i64 24)\n")
|
|
emith(" %isneg = icmp slt i64 %n0, 0\n %neg = sub i64 0, %n0\n")
|
|
emith(" %n = select i1 %isneg, i64 %neg, i64 %n0\n")
|
|
emith(" %term = getelementptr inbounds i8, ptr %buf, i32 23\n store i8 0, ptr %term\n")
|
|
emith(" %iszero = icmp eq i64 %n0, 0\n br i1 %iszero, label %zc, label %dl\n")
|
|
emith("zc:\n store i8 48, ptr %buf\n %z1 = getelementptr inbounds i8, ptr %buf, i32 1\n store i8 0, ptr %z1\n ret ptr %buf\n")
|
|
emith("dl:\n br label %dloop\n")
|
|
emith("dloop:\n %pos = phi i32 [ 22, %dl ], [ %pos2, %dbody ]\n %cur = phi i64 [ %n, %dl ], [ %cur2, %dbody ]\n")
|
|
emith(" %done = icmp eq i64 %cur, 0\n br i1 %done, label %sign, label %dbody\n")
|
|
emith("dbody:\n %d = urem i64 %cur, 10\n %d32 = trunc i64 %d to i32\n %ch = add i32 %d32, 48\n %ch8 = trunc i32 %ch to i8\n")
|
|
emith(" %pp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 %ch8, ptr %pp\n")
|
|
emith(" %cur2 = udiv i64 %cur, 10\n %pos2 = sub i32 %pos, 1\n br label %dloop\n")
|
|
emith("sign:\n br i1 %isneg, label %addneg, label %fin\n")
|
|
emith("addneg:\n %sp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 45, ptr %sp\n %posn = sub i32 %pos, 1\n br label %fin\n")
|
|
emith("fin:\n %fpos = phi i32 [ %pos, %sign ], [ %posn, %addneg ]\n")
|
|
emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n")
|
|
# the digits to the buffer's start, so what is returned is what was malloc'd and can be freed
|
|
emith(" %cnt = sub i32 24, %rpos\n %cnt64 = sext i32 %cnt to i64\n call ptr @memmove(ptr %buf, ptr %res, i64 %cnt64)\n ret ptr %buf\n}\n")
|
|
}
|
|
|
|
# s[a..b] -> a fresh NUL-terminated copy of the bytes [a, b), emitted (once) into
|
|
# any program that slices a string. Mallocs (b-a)+1, copies, terminates.
|
|
function emit_str_slice() -> void {
|
|
emith("define ptr @lp_str_slice(ptr %s, i32 %start, i32 %end) {\n")
|
|
emith("entry:\n %len = sub i32 %end, %start\n %sz = add i32 %len, 1\n")
|
|
emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @lp_malloc(i64 %sz64)\n br label %loop\n")
|
|
emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %body ]\n")
|
|
emith(" %d = icmp slt i32 %i, %len\n br i1 %d, label %body, label %fin\n")
|
|
emith("body:\n %si = add i32 %start, %i\n %sp = getelementptr inbounds i8, ptr %s, i32 %si\n %c = load i8, ptr %sp\n")
|
|
emith(" %op = getelementptr inbounds i8, ptr %out, i32 %i\n store i8 %c, ptr %op\n %i1 = add i32 %i, 1\n br label %loop\n")
|
|
emith("fin:\n %tp = getelementptr inbounds i8, ptr %out, i32 %len\n store i8 0, ptr %tp\n ret ptr %out\n}\n")
|
|
}
|