In the process of optimizing my project, I (and a LLM) found that I had to manually split a recursion function into a non-recur cold path + a recursive helper in order to reduce volume of direct copy:
![]()
I wouldnt call this a bug, but I do think this can be optimized into generating same bytecode(?)
Also, via LLMs disassembly, the splitted version will result larger on stack allocation, however for n iterations, the copies in terms of bytes decreases from (2n+2) * 1016 + 2n*779 to (2n+2) * 1015 + 1015 * n.
The code below is a minimal PoC generated by a LLM from my repo.
app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.26.0/GddgvsGjiNXrHFW6au5i5dUDbTs8FinQdrgE2Gqmw53t.tar.zst" }
import pf.OsStr
import pf.Stdout
Quad : { a : U64, b : U64, c : U64, d : U64 }
Block : { a : Quad, b : Quad, c : Quad, d : Quad }
Regs : { a : Block, b : Block, c : Block, d : Block }
Pad : { a : Regs, b : Block, c : Block, d : Quad }
Bus := { bytes : List(U8), l1 : List(U8), l2 : List(U8), l3 : List(U8), l4 : List(U8), l5 : List(U8), l6 : List(U8), l7 : List(U8), l8 : List(U8), regs : Pad }
Machine :: { bus : Bus, now : U64, retained : List(U8) }
view = |m| {
bus: { bytes: m.bus.bytes, l1: m.bus.l1, l2: m.bus.l2, l3: m.bus.l3, l4: m.bus.l4,
l5: m.bus.l5, l6: m.bus.l6, l7: m.bus.l7, l8: m.bus.l8, regs: m.bus.regs },
now: m.now, retained: m.retained,
}
make : U64 -> Machine
make = |seed| {
q = { a: seed, b: seed.plus_wrap(1), c: seed.plus_wrap(2), d: seed.plus_wrap(3) }
block = { a: q, b: q, c: q, d: q }
regs = { a: { a: block, b: block, c: block, d: block }, b: block, c: block, d: q }
bytes = List.repeat(seed.to_u8_wrap(), 16)
{ bus: { bytes, l1: bytes, l2: bytes, l3: bytes, l4: bytes, l5: bytes, l6: bytes, l7: bytes, l8: bytes, regs }, now: seed, retained: bytes }
}
advance : Machine, U64 -> Try(Machine, [Backwards, OutOfBounds])
advance = |m, target| {
if target < m.now { Err(Backwards) }
else if target - m.now > 2 {
changed = m.bus.bytes.set(0, m.bus.bytes.get(0)?.plus_wrap(1))?
advance({ ..m, now: m.now + 2, bus: { ..m.bus, bytes: changed } }, target)
} else { Ok({ ..m, now: target }) }
}
# The splitted version, performs a bit better
# advance : Machine, U64 -> Try(Machine, [Backwards, OutOfBounds])
# advance = |m, target| {
# if target < m.now { Err(Backwards) }
# else if target - m.now > 2 { advance_events(m, target) }
# else { Ok({ ..m, now: target }) }
# }
# advance_events : Machine, U64 -> Try(Machine, [Backwards, OutOfBounds])
# advance_events = |m, target| {
# if target < m.now { Err(Backwards) }
# else if target - m.now > 2 {
# changed = m.bus.bytes.set(0, m.bus.bytes.get(0)?.plus_wrap(1))?
# advance_events({ ..m, now: m.now + 2, bus: { ..m.bus, bytes: changed } }, target)
# } else { Ok({ ..m, now: target }) }
# }
candidate : Machine, U64, U64 -> Try(Machine, [Backwards, OutOfBounds])
candidate = |m, step, count| {
if count == 0 { Ok(m) }
else { candidate(advance(m, m.now.plus_wrap(step))?, step, count - 1) }
}
result_view = |r| match r { Ok(m) => Ok(view(m)) Err(e) => Err(e) }
exercise! : Machine, U64 => Try({}, _)
exercise! = |m, i| {
if i == 24 { return Ok({}) }
step = i.mod_by(12)
a = candidate(m, step, i)
b = candidate(m, step, i + 1)
Stdout.line!(Str.inspect((result_view(a), result_view(b))))?
exercise!(m, i + 1)
}
main! : List(OsStr) => Try({}, _)
main! = |args| {
seed = U64.from_str(OsStr.to_str_try(args.get(0)?)?)?
m = advance(make(seed), seed)?
exercise!(m, 0)?
Stdout.line!(Str.inspect(view(m)))?
Ok({})
}
Hi @zhen,
Could you make a GitHub issue for this?
Anton said:
Hi @zhen,
Could you make a GitHub issue for this?
Created.
https://github.com/roc-lang/roc/issues/12826
Last updated: Nov 04 2026 at 18:54 UTC