compiler: free a string an expression made once it has been used
The left half of a + chain, a template's pieces and holes, a number's text and a side made only to be compared are marked fresh and freed after the +, ==, != or print that reads them. lp_int_str and lp_long_str move their digits to the start of the buffer, so the pointer they return is the one malloc gave. Reseeded. examples/lang/string_temps.ludic: kept intermediates stay good, and 20,000 rounds grow the heap 0 bytes (2.9 MB before). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2fe4018d28
commit
9660587e10
10 changed files with 56019 additions and 48735 deletions
|
|
@ -695,9 +695,9 @@ function emit_call(e: Node) -> Val {
|
|||
let a = emit_expr(e.kids[0])
|
||||
if (llty(a.ty) == "ptr") { return a }
|
||||
if is_fp(a.ty) { return emit_fp_str(a) }
|
||||
if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @lp_long_str(i64 {a.code})`), "string") }
|
||||
if (llty(a.ty) == "i64") { g_uses_longstr = true; return fresh_val(emit_bind(`call ptr @lp_long_str(i64 {a.code})`), "string") }
|
||||
g_uses_intstr = true
|
||||
return val(emit_bind(`call ptr @lp_int_str(i32 {a.code})`), "string")
|
||||
return fresh_val(emit_bind(`call ptr @lp_int_str(i32 {a.code})`), "string")
|
||||
}
|
||||
if (name == "print") { # print(x): a value + newline (string, long, or int)
|
||||
var a = emit_expr(e.kids[0])
|
||||
|
|
@ -705,6 +705,7 @@ function emit_call(e: Node) -> Val {
|
|||
if (llty(a.ty) == "ptr") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr " + `{a.code})\n`) }
|
||||
else { if (llty(a.ty) == "i64") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_long, i64 " + `{a.code})\n`) }
|
||||
else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) } }
|
||||
if a.fresh { emit(` call void @free(ptr {a.code})\n`) } # a template printed is held by nothing
|
||||
return val("0", "void")
|
||||
}
|
||||
if (name == "bytes") { # bytes(n): allocate n bytes -> a byte buffer
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
# lit: the expression of a value made only of numeric literals with at least one
|
||||
# decimal among them (`1.5`, `-0.25`, `1.0 / 3.0`). It is `fixed` on its own and
|
||||
# is re-evaluated exactly in a float context — see emit_float.ludic.
|
||||
property Val { code: pointer = null, ty: pointer = null, lit: Node = null }
|
||||
function val(code: pointer, ty: pointer) -> Val { let v = new Val; v.code = code; v.ty = ty; v.lit = null; return v }
|
||||
# `fresh`: text this expression just malloc'd (a concatenation, a number's text) that nothing else holds
|
||||
property Val { code: pointer = null, ty: pointer = null, lit: Node = null, fresh: bool = false }
|
||||
function fresh_val(code: pointer, ty: pointer) -> Val { let v = val(code, ty); v.fresh = true; return v }
|
||||
function val(code: pointer, ty: pointer) -> Val { let v = new Val; v.code = code; v.ty = ty; v.lit = null; v.fresh = false; return v }
|
||||
|
||||
var head: Buf # module-level: types, globals, string constants
|
||||
var code: Buf # function bodies
|
||||
|
|
|
|||
|
|
@ -133,9 +133,17 @@ function to_long(v: Val) -> pointer {
|
|||
function emit_str_op(op: pointer, a: Val, b: Val) -> Val {
|
||||
g_uses_str = true
|
||||
if (op == ("+")) {
|
||||
return val(emit_bind(`call ptr @lp_str_concat(ptr {a.code}, ptr {b.code})`), "string")
|
||||
let r = fresh_val(emit_bind(`call ptr @lp_str_concat(ptr {a.code}, ptr {b.code})`), "string")
|
||||
# a half this expression made is copied now and held by nothing: `a + "/" + b` and every
|
||||
# template's pieces once left one string behind per `+`
|
||||
if a.fresh { emit(` call void @free(ptr {a.code})\n`) }
|
||||
if b.fresh and b.code != a.code { emit(` call void @free(ptr {b.code})\n`) }
|
||||
return r
|
||||
}
|
||||
let r = emit_bind(`call i32 @lp_str_eq(ptr {a.code}, ptr {b.code})`)
|
||||
# a side made just to be compared is held by nothing once it has been: `` `{x}` == "..." ``
|
||||
if a.fresh { emit(` call void @free(ptr {a.code})\n`) }
|
||||
if b.fresh and b.code != a.code { emit(` call void @free(ptr {b.code})\n`) }
|
||||
if (op == ("!=")) {
|
||||
let c = emit_bind(`icmp eq i32 {r}, 0`)
|
||||
return val(emit_bind(`zext i1 {c} to i32`), "bool")
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ function emit_fp_str(v: Val) -> Val {
|
|||
var d = v.code
|
||||
var single = "0"
|
||||
if (v.ty == "float") { d = emit_bind(`fpext float {v.code} to double`); single = "1" }
|
||||
return val(emit_bind(`call ptr @lp_fp_str(double {d}, i32 {single})`), "string")
|
||||
return fresh_val(emit_bind(`call ptr @lp_fp_str(double {d}, i32 {single})`), "string")
|
||||
}
|
||||
|
||||
# @lp_fp_str(v, single): "%.*g" with the fewest digits that round-trip (through
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ function emit_header() -> void {
|
|||
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")
|
||||
|
|
@ -292,7 +293,7 @@ function emit_str_prelude() -> void {
|
|||
|
||||
# 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 returns a pointer into the buffer.
|
||||
# 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 @malloc(i64 24)\n")
|
||||
|
|
@ -300,7 +301,7 @@ function emit_int_str() -> void {
|
|||
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 %zp = getelementptr inbounds i8, ptr %buf, i32 22\n store i8 48, ptr %zp\n ret ptr %zp\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")
|
||||
|
|
@ -310,7 +311,9 @@ function emit_int_str() -> void {
|
|||
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 ret ptr %res\n}\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
|
||||
|
|
@ -323,7 +326,7 @@ function emit_long_str() -> void {
|
|||
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 %zp = getelementptr inbounds i8, ptr %buf, i32 22\n store i8 48, ptr %zp\n ret ptr %zp\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")
|
||||
|
|
@ -333,7 +336,9 @@ function emit_long_str() -> void {
|
|||
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 ret ptr %res\n}\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
|
||||
|
|
|
|||
52330
selfhost/ludicc.seed.ll
52330
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue