Hi there, I am working on an emulator rn, and trying to optimize the backend for better frame rate.
In this process, I nuked one intermediate var and did a direct return. However, somehow this worsen the performance(in terms of frame rate).
I (and a LLM) observed that this should-have-been-equivalent tiny changes resulted extra on-stack allocation in the generated binary.
![]()
The compiler I used is the latest nightly released on Nov.2nd, and the platform is X86-64 Linux, with flag --opt=speed --debug, nothing special.
I have a snippet below, it was written by an LLM based on my codebase as a concise PoC.
Am I doing smth wrong here?
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
# Pad is 800 inline bytes; Bus is 1016 bytes on x86-64 (nine List descriptors).
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) }
ReadResult(a) : { val : a, state : [Unchanged, Updated(Box(Bus))] }
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 }) }
}
read16 : Bus, U32 -> Try(ReadResult(U16), [OutOfBounds, ReadError])
read16 = |bus, addr| {
# Keep a non-inline read shape. The exercised addresses are 0..20.
if addr >= 32 {
changed = bus.bytes.set(0, bus.bytes.get(0)?.plus_wrap(1))?
return read16({ ..bus, bytes: changed }, addr - 32)
}
val = bus.bytes.get(addr.to_u64())?
match addr.mod_by(4) {
0 => Ok({ val: val.to_u16(), state: Unchanged })
1 => Ok({ val: val.to_u16(), state: Updated(Box.box({ ..bus, bytes: bus.bytes.set(addr.to_u64(), val.plus_wrap(1))? })) })
2 => Ok({ val: val.to_u16(), state: Unchanged })
_ => Err(ReadError)
}
}
read32 : Bus, U32 -> Try(ReadResult(U32), [OutOfBounds, ReadError])
read32 = |bus, addr| {
first = read16(bus, addr)?
Ok({ val: first.val.to_u32(), state: first.state })
}
candidate : Machine, [W16, W32], U32 -> Try((Machine, U32), _)
candidate = |m, width, addr| {
advanced = advance(m, m.now.plus_wrap(addr.to_u64()))?
intermediate = match width {
W16 => {
inner = read16(advanced.bus, addr)?
{ val: inner.val.to_u32(), state: inner.state }
}
W32 => read32(advanced.bus, addr)?
}
# substitute here
new_bus = match intermediate.state {
Unchanged => advanced.bus
Updated(x) => Box.unbox(x)
}
Ok(({ ..advanced, bus: new_bus }, intermediate.val))
# match intermediate.state {
# Unchanged => Ok((advanced, intermediate.val))
# Updated(x) => Ok(({ ..advanced, bus: Box.unbox(x) }, intermediate.val))
# }
}
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,
}
result_view = |r| match r { Ok((m, val)) => Ok((view(m), val)) Err(e) => Err(e) }
exercise! : Machine, U32 => Try({}, _)
exercise! = |m, addr| {
if addr == 20 { return Ok({}) }
width = if addr.mod_by(2) == 0 W16 else W32
a = candidate(m, width, addr)
b = candidate(m, width, addr + 1)
Stdout.line!(Str.inspect((result_view(a), result_view(b))))?
exercise!(m, addr + 1)
}
main! : List(OsStr) => Try({}, _)
main! = |args| {
seed = U64.from_str(OsStr.to_str_try(args.get(0)?)?)?
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)
m = advance({ bus: { bytes, l1: bytes, l2: bytes, l3: bytes, l4: bytes, l5: bytes, l6: bytes, l7: bytes, l8: bytes, regs }, now: seed, retained: bytes }, seed)?
exercise!(m, 0)?
for saved in [m.bus.bytes, m.bus.l1, m.bus.l2, m.bus.l3, m.bus.l4, m.bus.l5, m.bus.l6, m.bus.l7, m.bus.l8, m.retained] {
if saved.len() != 16 { return Err(SnapshotChanged) }
for byte in saved { if byte != seed.to_u8_wrap() { return Err(SnapshotChanged) } }
}
if m.now != seed or m.bus.regs != regs { return Err(SnapshotChanged) }
Stdout.line!(Str.inspect(view(m)))?
Ok({})
}
Looks like a bug -- I had Opus 5.5 dig in to find the root cause and made this issue
https://github.com/roc-lang/roc/issues/12819
3 messages were moved from this topic to #beginners > HTML templating by Anton.
Last updated: Nov 04 2026 at 18:54 UTC