Programs often size a table by repeating a literal: @bg_pixels = [0] * 16, @sp_map = [nil] * 264. The class
pools (ADR 0296) join the class of every value stored into an ivar. They could
not name the class of [0] * 16, so every such ivar lost its pool, and each @sp_map[@hclk] kept the
mrb_array_p(...) && ...->c == M->array_class test in front of the element read.
CodeGen#native_array_times_result (tools/bc2cpp/codegen_native_results.rb) answers ARR for a * send when:
- the receiver is exactly an Array and the argument is proven an Integer;
- the native body is the audited
mrb_ary_times(NativeCoreDirect::RESULT_ENTRIES). It returnsmrb_ary_newfor a zero count or its ownary_new_capa, both exactlyArray, and raises for a negative count. Nothing in the program defines, installs or prepends*on Array (native_core_entry_safe?); - nothing can give an Integer a
to_str.mrb_ary_timesfirst triesmrb_check_string_typeon the argument and joins when that converts, as[1, 2] * ","does. So the registry may hold noto_stron Integer's ancestors, no installer or outside Ruby source may spell it, and no native outside mruby's core may spell it; - no ancestor of Array defines
*.
The last condition lets the fact skip the usual refusal for a name a computed send may remove. Optcarrot's
send(*DISPATCH[@opcode]) counts as a possible remove_method of every name. If Array#* were removed, the send
would either raise NoMethodError and produce no value, or reach an ancestor's *, and there is none. Either way,
every value the send does produce is a fresh Array.
BC2CPP_ARRAY_TIMES_RESULT=0 drops the fact.
MRBC=path/to/mrbc ruby scripts/bc2cpp/array_times_result_check.rb asserts that @cells = [0] * 8 and
@cells = [nil] * 4 give @cells an Array pool, in a world with a computed send(*op), and that its index read
drops the class test. These keep no pool: a String argument, an argument of unknown class, a program
Integer#to_str, a program Object#*, and the opt-out. Interpreted and compiled runs print the same, including
the String join and the negative-count ArgumentError.
These figures come from the optcarrot probe (Lan_Master, release -O3, callgrind). Instructions per frame are
the difference between runs of 21 and 1 frames, divided by 20. Checksum 59662 at 180 frames is unchanged. The
probe's ivar proof is only on with the ivar proof scope change, so the build
measured here also has that change, resumable locals and
HOT_INLINE, on master 479b31ff.
| Build | Instructions per frame | Ivar class pools |
|---|---|---|
| Before | 32,191,709 | 42 |
| With ARRAY_TIMES_RESULT | 30,589,709 | 48 |
PPU#@bg_pixels, @sp_map, @sp_buffer, @output_color, @sp_ram and ROM#@prg_ref gain pools.
PPU#render_pixel alone drops by 0.62M, and the frame as a whole by 1.60M (5.0%). Startup is unchanged.