mrbc compiles the break and next of a while loop (and redo/retry) to OP_JMPUW. NumericFlow
(tools/bc2cpp/numeric_flow.rb) used to refuse any irep containing one. The flow returned no state for it, so:
- the irep's own arithmetic and compares got no
NUMERIC_OPERAND_PROOFarms; - every
SETIVin it failed its ivar group (NUMERIC_IVAR_PROOF), and with it that ivar's proof and class pool in every method of the program; - every
SETCONSTin it failed its constant group in the same way.
The resumable Optcarrot::PPU#main_loop ends its scanline loop with while true ... break, so it was one of these
ireps. Its stores of @hclk, @scanline and @sp_phase failed those ivars for all of PPU.
vm.c's OP_JMPUW throws to an ensure handler only when irep->clen > 0 and an ensure handler covers the jump and
not its target. Otherwise it sets pc to the target, exactly like OP_JMP. With no catch handler in the irep
(!program.handlers?, the test CodeGen#jmpuw_is_plain_jump? already uses to compile JMPUW as goto), the IR's
successor edge to the target is the only edge. The flow reads the JMPUW as a JMP: it writes no register, and the
IR already lists its target as a successor (build_edges uses branch_target, which includes JMPUW).
A JMPUW in an irep with a rescue or ensure stays unsupported, since it may run the ensure body before it
reaches its target. BC2CPP_NUMERIC_JMPUW=0 leaves every JMPUW irep unmodelled, as before.
Modelling more ireps exposed two memos that a core-body compile could poison. compile_method compiles mruby's own
Ruby with @closed_world withheld, and compile probes run before compute_numeric_facts. When a core irep with a
JMPUW was modelled during such a probe, its constant reads called lexical_const_info first, which then memoized
the empty closed-world-less analysis for the whole run. Every NUMCONST fact of optcarrot disappeared.
lexical_const_info and numeric_nil_raises? now answer a core body without memoizing.
A resumable step function gets the same proof-driven arms as any other compiled method: no proof code checks
@resumable. Its registers are mrb_value locals (RESUMABLE_LOCAL_REGISTERS), as in every other method. bc2cpp has
no unboxed mrb_int register representation. A Fixnum proof drops a tag test, and GCC removes the boxing in between.
In optcarrot the hot registers of main_loop hold @hclk and @hclk_target, and this change still proves neither:
@hclk += 1reads an ivar whose entry mask includes nil (reset, notinitialize, assigns it). The old file-level nil-operator scan listed+becausemruby-rationaldefinesnil.to_rnext toRational#+. The scan now reads each registration's owner and@hclkis proven (NIL_NATIVE_ANSWERS).@hclk_targetand@vclkare assigned by destructuring the frozenDUMMY_FRAME/BOOT_FRAMEArrays, and fromPPU#sync's argument. Neither of those values is typed.- An Integer proof also covers a bigint. Only a Fixnum interval would drop the tag tests of
@hclk += 1and@hclk_target <= @hclk, and a counter that only grows has no such interval.
MRBC=path/to/mrbc ruby scripts/bc2cpp/numeric_jmpuw_check.rb compiles a Fiber root that writes its ivars around a
while true ... break and a next, a plain method with the same loop shape, and a break inside begin ... ensure.
It asserts the NUMIVAR facts and the NUMERIC_OPERAND_PROOF arms in the step function and its inlined helper. It
asserts that the ensure method's ivar stays unproven and that BC2CPP_NUMERIC_JMPUW=0 drops the facts. It then
compares interpreted and compiled runs, where an Integer overflows to a bigint between two yields, a bigint is held
in a register across a resume, and a Float is stored into a proven ivar.
Optcarrot probe (Lan_Master, callgrind). Instructions per frame are (21 frames − 1 frame) / 20. Both builds come from
the same tree. Checksum 59662 at 180 frames.
| Build | Instructions per frame | 1-frame run (startup) |
|---|---|---|
BC2CPP_NUMERIC_JMPUW=0 |
26,507,449 | 1,098,898,702 |
| Default | 26,506,883 | 1,098,898,563 |
The generated optcarrot code changes in APU#reset_mapping (cold) and in one @sp_ram[0] read in main_loop, which
now takes the exact-Array path. The change is about soundness coverage. It adds no speed on this workload.