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>
355 lines
17 KiB
Text
355 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 {
|
|
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 @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 @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 @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 @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")
|
|
}
|