ruby-changes:73366
From: John <ko1@a...>
Date: Fri, 2 Sep 2022 07:21:17 +0900 (JST)
Subject: [ruby-changes:73366] 679ef34586 (master): New constant caching insn: opt_getconstant_path
https://git.ruby-lang.org/ruby.git/commit/?id=679ef34586 From 679ef34586e7a43151865cb7f33a3253d815f7cf Mon Sep 17 00:00:00 2001 From: John Hawthorn <john@h...> Date: Wed, 10 Aug 2022 10:35:48 -0700 Subject: New constant caching insn: opt_getconstant_path Previously YARV bytecode implemented constant caching by having a pair of instructions, opt_getinlinecache and opt_setinlinecache, wrapping a series of getconstant calls (with putobject providing supporting arguments). This commit replaces that pattern with a new instruction, opt_getconstant_path, handling both getting/setting the inline cache and fetching the constant on a cache miss. This is implemented by storing the full constant path as a null-terminated array of IDs inside of the IC structure. idNULL is used to signal an absolute constant reference. $ ./miniruby --dump=insns -e '::Foo::Bar::Baz' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,13)> (catch: FALSE) 0000 opt_getconstant_path <ic:0 ::Foo::Bar::Baz> ( 1)[Li] 0002 leave The motivation for this is that we had increasingly found the need to disassemble the instructions between the opt_getinlinecache and opt_setinlinecache in order to determine the constant we are fetching, or otherwise store metadata. This disassembly was done: * In opt_setinlinecache, to register the IC against the constant names it is using for granular invalidation. * In rb_iseq_free, to unregister the IC from the invalidation table. * In YJIT to find the position of a opt_getinlinecache instruction to invalidate it when the cache is populated * In YJIT to register the constant names being used for invalidation. With this change we no longe need disassemly for these (in fact rb_iseq_each is now unused), as the list of constant names being referenced is held in the IC. This should also make it possible to make more optimizations in the future. This may also reduce the size of iseqs, as previously each segment required 32 bytes (on 64-bit platforms) for each constant segment. This implementation only stores one ID per-segment. There should be no significant performance change between this and the previous implementation. Previously opt_getinlinecache was a "leaf" instruction, but it included a jump (almost always to a separate cache line). Now opt_getconstant_path is a non-leaf (it may raise/autoload/call const_missing) but it does not jump. These seem to even out. --- benchmark/vm_const.yml | 6 + common.mk | 2 +- compile.c | 205 ++++++++++----- insns.def | 63 ++--- iseq.c | 140 +++++------ iseq.h | 1 + test/ruby/test_mjit.rb | 6 +- test/ruby/test_yjit.rb | 8 +- .../views/_mjit_compile_getconstant_path.erb | 30 +++ .../ruby_vm/views/_mjit_compile_getinlinecache.erb | 31 --- tool/ruby_vm/views/mjit_compile.inc.erb | 4 +- vm_core.h | 19 +- vm_insnhelper.c | 82 +++--- yjit.h | 4 +- yjit/src/codegen.rs | 29 +-- yjit/src/cruby_bindings.inc.rs | 280 ++++++++++----------- yjit/src/invariants.rs | 82 +++--- 17 files changed, 512 insertions(+), 480 deletions(-) create mode 100644 tool/ruby_vm/views/_mjit_compile_getconstant_path.erb delete mode 100644 tool/ruby_vm/views/_mjit_compile_getinlinecache.erb diff --git a/benchmark/vm_const.yml b/benchmark/vm_const.yml index 6064d4eed0..8939ca0cd3 100644 --- a/benchmark/vm_const.yml +++ b/benchmark/vm_const.yml @@ -1,7 +1,13 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/vm_const.yml#L1 prelude: | Const = 1 + A = B = C = D = E = F = G = H = I = J = K = L = M = N = O = P = Q = R = S = T = U = V = W = X = Y = Z = 1 + def foo + A; B; C; D; E; F; G; H; I; J; K; L; M; N; O; P; Q; R; S; T; U; V; W; X; Y; Z + end benchmark: vm_const: | j = Const k = Const + vm_const_many: | + foo loop_count: 30000000 diff --git a/common.mk b/common.mk index 3d3976d5d5..86226b58ca 100644 --- a/common.mk +++ b/common.mk @@ -1045,7 +1045,7 @@ $(srcs_vpath)mjit_compile.inc: $(tooldir)/ruby_vm/views/mjit_compile.inc.erb $(i https://github.com/ruby/ruby/blob/trunk/common.mk#L1045 $(tooldir)/ruby_vm/views/_mjit_compile_insn.erb $(tooldir)/ruby_vm/views/_mjit_compile_send.erb \ $(tooldir)/ruby_vm/views/_mjit_compile_ivar.erb \ $(tooldir)/ruby_vm/views/_mjit_compile_insn_body.erb $(tooldir)/ruby_vm/views/_mjit_compile_pc_and_sp.erb \ - $(tooldir)/ruby_vm/views/_mjit_compile_invokebuiltin.erb $(tooldir)/ruby_vm/views/_mjit_compile_getinlinecache.erb + $(tooldir)/ruby_vm/views/_mjit_compile_invokebuiltin.erb $(tooldir)/ruby_vm/views/_mjit_compile_getconstant_path.erb BUILTIN_RB_SRCS = \ $(srcdir)/ast.rb \ diff --git a/compile.c b/compile.c index e906bd1e10..3e51c5cd53 100644 --- a/compile.c +++ b/compile.c @@ -2251,6 +2251,30 @@ add_adjust_info(struct iseq_insn_info_entry *insns_info, unsigned int *positions https://github.com/ruby/ruby/blob/trunk/compile.c#L2251 return TRUE; } +static ID * +array_to_idlist(VALUE arr) +{ + RUBY_ASSERT(RB_TYPE_P(arr, T_ARRAY)); + long size = RARRAY_LEN(arr); + ID *ids = (ID *)ALLOC_N(ID, size + 1); + for (int i = 0; i < size; i++) { + VALUE sym = RARRAY_AREF(arr, i); + ids[i] = SYM2ID(sym); + } + ids[size] = 0; + return ids; +} + +static VALUE +idlist_to_array(const ID *ids) +{ + VALUE arr = rb_ary_new(); + while (*ids) { + rb_ary_push(arr, ID2SYM(*ids++)); + } + return arr; +} + /** ruby insn object list -> raw instruction sequence */ @@ -2433,6 +2457,21 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) https://github.com/ruby/ruby/blob/trunk/compile.c#L2457 } /* [ TS_IVC | TS_ICVARC | TS_ISE | TS_IC ] */ case TS_IC: /* inline cache: constants */ + { + unsigned int ic_index = ISEQ_COMPILE_DATA(iseq)->ic_index++; + IC ic = &ISEQ_IS_ENTRY_START(body, type)[ic_index].ic_cache; + if (UNLIKELY(ic_index >= body->ic_size)) { + BADINSN_DUMP(anchor, &iobj->link, 0); + COMPILE_ERROR(iseq, iobj->insn_info.line_no, + "iseq_set_sequence: ic_index overflow: index: %d, size: %d", + ic_index, ISEQ_IS_SIZE(body)); + } + + ic->segments = array_to_idlist(operands[j]); + + generated_iseq[code_index + 1 + j] = (VALUE)ic; + } + break; case TS_ISE: /* inline storage entry: `once` insn */ case TS_ICVARC: /* inline cvar cache */ case TS_IVC: /* inline ivar cache */ @@ -2447,11 +2486,6 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor) https://github.com/ruby/ruby/blob/trunk/compile.c#L2486 } generated_iseq[code_index + 1 + j] = (VALUE)ic; - if (insn == BIN(opt_getinlinecache) && type == TS_IC) { - // Store the instruction index for opt_getinlinecache on the IC for - // YJIT to invalidate code when opt_setinlinecache runs. - ic->get_insn_idx = (unsigned int)code_index; - } break; } case TS_CALLDATA: @@ -5233,6 +5267,30 @@ compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, https://github.com/ruby/ruby/blob/trunk/compile.c#L5267 return COMPILE_OK; } +static VALUE +collect_const_segments(rb_iseq_t *iseq, const NODE *node) +{ + VALUE arr = rb_ary_new(); + for (;;) + { + switch (nd_type(node)) { + case NODE_CONST: + rb_ary_unshift(arr, ID2SYM(node->nd_vid)); + return arr; + case NODE_COLON3: + rb_ary_unshift(arr, ID2SYM(node->nd_mid)); + rb_ary_unshift(arr, ID2SYM(idNULL)); + return arr; + case NODE_COLON2: + rb_ary_unshift(arr, ID2SYM(node->nd_mid)); + node = node->nd_head; + break; + default: + return Qfalse; + } + } +} + static int compile_const_prefix(rb_iseq_t *iseq, const NODE *const node, LINK_ANCHOR *const pref, LINK_ANCHOR *const body) @@ -8970,37 +9028,31 @@ compile_match(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i https://github.com/ruby/ruby/blob/trunk/compile.c#L9028 static int compile_colon2(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) { - const int line = nd_line(node); if (rb_is_const_id(node->nd_mid)) { /* constant */ - LABEL *lend = NEW_LABEL(line); - int ic_index = ISEQ_BODY(iseq)->ic_size++; - - DECL_ANCHOR(pref); - DECL_ANCHOR(body); - - INIT_ANCHOR(pref); - INIT_ANCHOR(body); - CHECK(compile_const_prefix(iseq, node, pref, body)); - if (LIST_INSN_SIZE_ZERO(pref)) { - if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache) { - ADD_INSN2(ret, node, opt_getinlinecache, lend, INT2FIX(ic_index)); - } - else { + VALUE segments; + if (ISEQ_COMPILE_DATA(iseq)->option->inline_const_cache && + (segments = colle (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/