Repository navigation
The inherited-hook pass finds classes, Structs and modules through an index built once - #8467
Conversation
… index built once desugar_inherited_hooks asked inh_chain_has_hook, once per class with a superclass, and every ask scanned all of the program's nodes for each ancestor step, as inh_module_hook did for each `extend` it met. The pass was K asks times n nodes, which `make scale-test` showed as the front end's work at 4x the program rising from 4.93 to 5.00 once the hooks reached extended modules and Struct.new blocks (their per-node struct test came first in that scan). The pass now builds, once, a hashed index of the names the questions are about: for a class or Struct constant, whether a body of it defines a hook or extends a module that does, and the superclass name its first `class` gives; for a module, the `def inherited` of the first module of that name that has one, how many bodies extend it and whether one of them sits below a class with a hook. An ancestor walk is then one lookup per link, at most 64 links as before, and the module loop reads the counted extenders instead of rescanning the program per module. The once-per-class set of qualified names is hashed as well. A program in which no class, Struct or module has a hook returns before the parent table is built. The generated C is unchanged. test/class_inherited_hook_index.rb pins the shapes the index has to answer as the scans did: a module reopened after a copy without the hook, several `extend`s, a reopened class, a hook above and below another, a Struct block with `extend`, and a module no class extends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Gate: green tree 78326273cd93 master 123eaf3 (darwin-arm64 clang-21.0.0) tests 6835/0
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to This change speeds up inherited-hook processing in the compiler and is reported to produce identical generated C. It adds regression fixtures for inheritance and extension-order cases. No concrete defects were identified, so it appears ready to merge. Pre-merge checks |
|
Probed at master
61dedaeac, macOS, Apple clang 21.make scale-test's "work at 4x the program" ratio rose from 4.93 to 5.00 after #8353 (Fire inherited hooks from extended modules and Struct.new blocks), which touched only the inherited-hook pass insrc/. The pass was already K x n0 before that change; #8353 doubled the constant.--emit-rbswork count oftest/scale/gen.shscale-testdesugar_inherited_hookscallsinh_chain_has_hookonce per class with a superclass (K classes in the scale program). Each call scans all n0 nodes for every ancestor step, so the walk is K x n0 whether or not the program has a hook anywhere. Inherited hooks fire from an extended module and a Struct.new block #8353 putinh_struct_body(annt_kindper node) ahead of theNK_ClassNodetest of that scan, which doubled the constant and moved the ratio.inh_module_hook, which scans all nodes for everyextendargument, for the module loop (every module with a hook scans every node and callsinh_extended_hookon each), and for the once-per-class set of qualified names (a linear list searched per class).One index per pass.
desugar_inherited_hooksbuilds a hashed index (ANameHash, ascompute_reachabledoes for called names) once: for a class or Struct constant name, whether a body of it definesinheritedor extends a module that does, and the superclass name its firstclassgives; for a module name, thedef inheritedof the first module of that name that has one. An ancestor walk (inh_chain_has_hook) is then one lookup per link, still at most 64 links and the first superclass each name gives. The module loop reads, per module, the number of bodies that extend it and whether one of them sits below a class with a hook, counted in a single pass over the program instead of a scan per module.inh_module_hookandinh_extended_hookbecomeinh_module_def_hook(one module) andinh_extended_entry(the module a body extends, through the index). The once-per-class set of qualified names is anANameHashas well. When no class, Struct or module of the program has a hook, the pass returns before it builds the parent table: with no hook there is nosuperto turn into nil and no call to place. The index lives in a local and is freed at the end; there is no new global or file-scope table.The answers are the scans' answers. A name's hook is the OR over all its definitions, which the scan also returned at the first match; the superclass is the first
classof the name in node order that gives a constant superclass, which is what!super_namekept; the module's hook is the first module of that name that has one, whichinh_module_hookreturned. The tests of the old loops keep their order and arguments (asuperis still read withnt_str(sc, "name")without a kind test in the two places that had none). The tree edits the pass makes (supertonilin a hook body, a call prepended to a class body, cloned nodes pastn0) change none of the facts the index holds.Cost. Work count of the pass alone (
g_nt_workaround the call), master -> branch:gen.sh 100gen.sh 400The padded program has per unit a hook module, a class under a class with a hook, a class that extends the module, a class under that, a Struct block with a hook and a class two levels down. The branch is linear in it (4.0x per 4x). Whole
--emit-rbswork on it: 50 units 30,953,669 -> 26,007,515, 200 units 590,216,718 -> 330,956,799; the rest of the total there is other passes (class structure), not covered here.The corpus runs come from the local
make gatebelow (this branch merged with master123eaf323, macOS arm64, clang 21); the corpus-wide C diff was not run.Name test. The one
sp_streq(dn, "inherited")test in the pass is the oldinh_module_hook's, moved unchanged into the index build.Tests.
test/class_inherited_hook_index.rb(new) pins the shapes the index answers as the scans did: a module reopened after a copy without the hook, severalextends on one class, a reopened class (one call), hooks above and below one another, a Struct block withextend,class << self, and a module that no class extends. It passes on master as well (the change is not a behaviour change) and on this branch plain, with--int-overflow=promote, with--share-strings, and underSPINEL_GC_STRESS=1;test/class_inherited_extend_struct.rbandtest/class_inherited_hook.rbpass the same four ways. The generated C of 1,126 programs (everytest/*.rbandtest/share/*.rbthat mentionsinherited,Struct.new,Data.define,extendor a subclass,benchmark/*.rb, and optcarrot) is byte-identical to master in the default build and with--share-strings, and so is that of four further shape programs for the module, extend and chain cases. In that gatemake testpasses (6835 pass, 0 fail, 0 error) and the corpus with sharing on has 6833 pass, 2 known failures (already listed intest/share/known-failures.txt) and 0 new failures.make gate(on this branch merged with current master)Local
make gateon this branch merged with master123eaf323(macOS arm64, clang 21); the head commit carries its Gate trailer..expectedfiles that match CRuby 4.0 run with--enable-frozen-string-literal# spinel: int64🤖 Generated with Claude Code
Summary by CodeRabbit
superand classes reopened in multiple places.Structsubclasses, nested constants, and extension order.