Part of #856 (perf epic). Lowest-risk, start-today candidate — correctness-neutral codegen cleanups that help every numeric path. Good for validating the cost model (#856) before the larger refactors.
Summary
The emitted IL contains pervasive redundant work that a small peephole / loop-invariant pass would remove. Two independent items, grouped because both are local IL-cleanup passes:
(a) Dead box → unbox round-trips and discarded boxed results
A value already typed float64 on the stack is boxed and then immediately coerced back via a $Runtime call:
// stringWork scan — s.length:
conv.r8
box Double
call $Runtime::ConvertToNumber(object) // <-- box then immediately unbox
// stringWork — charCodeAt result, and again for the index arg:
box Double → $Runtime::ToNumber(object)
box Double → $Runtime::ConvertToNumber(object)
And boxed values computed only to be thrown away — the push return (array length) is computed, boxed, and popped:
// countPrimes / arrayMethodWork after ArrayPush:
callvirt List`1::get_Count()
conv.r8
box Double
pop // <-- boxed for nothing
Fix: when the static stack type already matches the consumer, skip the box + $Runtime coercion; when a boxed result is unused, don't compute/box it.
(b) Loop-invariant recomputation (LICM/CSE)
stringWork's scan recomputes UnwrapStringReceiver(s) + get_Length() every iteration although s is not mutated in that loop; array loops recompute .Count similarly.
IL_005a: call string $Runtime::UnwrapStringReceiver(object) // loop-invariant
IL_005f: call instance int32 String::get_Length() // loop-invariant
Fix: hoist provably loop-invariant receiver-unwrap / length / count out of the loop header.
Notes
Scope / priority
Low risk, broad benefit. Good first perf PR.
Part of #856 (perf epic). Lowest-risk, start-today candidate — correctness-neutral codegen cleanups that help every numeric path. Good for validating the cost model (#856) before the larger refactors.
Summary
The emitted IL contains pervasive redundant work that a small peephole / loop-invariant pass would remove. Two independent items, grouped because both are local IL-cleanup passes:
(a) Dead box → unbox round-trips and discarded boxed results
A value already typed
float64on the stack is boxed and then immediately coerced back via a$Runtimecall:And boxed values computed only to be thrown away — the
pushreturn (array length) is computed, boxed, and popped:Fix: when the static stack type already matches the consumer, skip the
box+$Runtimecoercion; when a boxed result is unused, don't compute/box it.(b) Loop-invariant recomputation (LICM/CSE)
stringWork's scan recomputesUnwrapStringReceiver(s)+get_Length()every iteration althoughsis not mutated in that loop; array loops recompute.Countsimilarly.Fix: hoist provably loop-invariant receiver-unwrap / length / count out of the loop header.
Notes
--verify).Scope / priority
Low risk, broad benefit. Good first perf PR.