Skip to content

Commit 91ae698

Browse files
committed
Use compile-time flag to indicate dynamic CREFs
The inline constant cache previously used `RCLASS_SINGLETON_P` to detect "unstable" CREFs that need ic_cref stored and checked on every IC hit. This caused the `class << self` pattern to create inline caches which requires extra checks and can't optimize as well by the JITs. We can avoid this by replacing the `RCLASS_SINGLETON_P` check with a `VM_DEFINECLASS_FLAG_DYNAMIC_CREF` flag added to the defineclass instruction at compile time to indicate a dynamic class scope and specifically avoid setting it for the `class << self` pattern. We can apply the same logic to fix dynamic CREF on `module (expr)::Foo`. We can say that any class definition which uses only constant references is stable, or at least as stable as the cref it was declared inside. [Bug #20948]
1 parent 07e52c8 commit 91ae698

8 files changed

Lines changed: 122 additions & 14 deletions

File tree

compile.c

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6090,6 +6090,23 @@ compile_const_prefix(rb_iseq_t *iseq, const NODE *const node,
60906090
return COMPILE_OK;
60916091
}
60926092

6093+
static int
6094+
cpath_const_p(const NODE *node)
6095+
{
6096+
switch (nd_type(node)) {
6097+
case NODE_CONST:
6098+
case NODE_COLON3:
6099+
return TRUE;
6100+
case NODE_COLON2:
6101+
if (RNODE_COLON2(node)->nd_head) {
6102+
return cpath_const_p(RNODE_COLON2(node)->nd_head);
6103+
}
6104+
return TRUE;
6105+
default:
6106+
return FALSE;
6107+
}
6108+
}
6109+
60936110
static int
60946111
compile_cpath(LINK_ANCHOR *const ret, rb_iseq_t *iseq, const NODE *cpath)
60956112
{
@@ -6099,9 +6116,13 @@ compile_cpath(LINK_ANCHOR *const ret, rb_iseq_t *iseq, const NODE *cpath)
60996116
return VM_DEFINECLASS_FLAG_SCOPED;
61006117
}
61016118
else if (nd_type_p(cpath, NODE_COLON2) && RNODE_COLON2(cpath)->nd_head) {
6102-
/* Bar::Foo */
6119+
/* Bar::Foo or expr::Foo */
61036120
NO_CHECK(COMPILE(ret, "nd_else->nd_head", RNODE_COLON2(cpath)->nd_head));
6104-
return VM_DEFINECLASS_FLAG_SCOPED;
6121+
int flags = VM_DEFINECLASS_FLAG_SCOPED;
6122+
if (!cpath_const_p(RNODE_COLON2(cpath)->nd_head)) {
6123+
flags |= VM_DEFINECLASS_FLAG_DYNAMIC_CREF;
6124+
}
6125+
return flags;
61056126
}
61066127
else {
61076128
/* class at cbase Foo */
@@ -11477,9 +11498,20 @@ iseq_compile_each0(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const no
1147711498
CHECK(COMPILE(ret, "sclass#recv", RNODE_SCLASS(node)->nd_recv));
1147811499
ADD_INSN (ret, node, putnil);
1147911500
CONST_ID(singletonclass, "singletonclass");
11501+
11502+
/* `class << self` in a class body and `class << Foo` (constant
11503+
receiver) are stable. All other forms are potentially dynamic. */
11504+
int sclass_flags = VM_DEFINECLASS_TYPE_SINGLETON_CLASS;
11505+
const NODE *recv = RNODE_SCLASS(node)->nd_recv;
11506+
if (!(nd_type_p(recv, NODE_SELF) &&
11507+
ISEQ_BODY(iseq)->type == ISEQ_TYPE_CLASS) &&
11508+
!cpath_const_p(recv)) {
11509+
sclass_flags |= VM_DEFINECLASS_FLAG_DYNAMIC_CREF;
11510+
}
11511+
1148011512
ADD_INSN3(ret, node, defineclass,
1148111513
ID2SYM(singletonclass), singleton_class,
11482-
INT2FIX(VM_DEFINECLASS_TYPE_SINGLETON_CLASS));
11514+
INT2FIX(sclass_flags));
1148311515
RB_OBJ_WRITTEN(iseq, Qundef, (VALUE)singleton_class);
1148411516

1148511517
if (popped) {

eval_intern.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,10 @@ rb_ec_tag_jump(const rb_execution_context_t *ec, enum ruby_tag_type st)
172172

173173
/* CREF operators */
174174

175-
#define CREF_FL_PUSHED_BY_EVAL IMEMO_FL_USER1
176-
#define CREF_FL_OMOD_SHARED IMEMO_FL_USER2
177-
#define CREF_FL_SINGLETON IMEMO_FL_USER3
175+
#define CREF_FL_PUSHED_BY_EVAL IMEMO_FL_USER1
176+
#define CREF_FL_OMOD_SHARED IMEMO_FL_USER2
177+
#define CREF_FL_SINGLETON IMEMO_FL_USER3
178+
#define CREF_FL_DYNAMIC_CREF IMEMO_FL_USER4
178179

179180
static inline int CREF_SINGLETON(const rb_cref_t *cref);
180181

@@ -260,6 +261,18 @@ CREF_OMOD_SHARED_SET(rb_cref_t *cref)
260261
cref->flags |= CREF_FL_OMOD_SHARED;
261262
}
262263

264+
static inline int
265+
CREF_DYNAMIC(const rb_cref_t *cref)
266+
{
267+
return cref->flags & CREF_FL_DYNAMIC_CREF;
268+
}
269+
270+
static inline void
271+
CREF_DYNAMIC_SET(rb_cref_t *cref)
272+
{
273+
cref->flags |= CREF_FL_DYNAMIC_CREF;
274+
}
275+
263276
static inline void
264277
CREF_OMOD_SHARED_UNSET(rb_cref_t *cref)
265278
{

insns.def

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,10 +811,16 @@ defineclass
811811

812812
rb_iseq_check(class_iseq);
813813

814+
rb_cref_t *cref = vm_cref_push(ec, klass, NULL, FALSE, FALSE);
815+
816+
if (VM_DEFINECLASS_DYNAMIC_CREF_P(flags)) {
817+
CREF_DYNAMIC_SET(cref);
818+
}
819+
814820
/* enter scope */
815821
vm_push_frame(ec, class_iseq, VM_FRAME_MAGIC_CLASS | VM_ENV_FLAG_LOCAL, klass,
816822
GC_GUARDED_PTR(box),
817-
(VALUE)vm_cref_push(ec, klass, NULL, FALSE, FALSE),
823+
(VALUE)cref,
818824
ISEQ_BODY(class_iseq)->iseq_encoded, GET_SP(),
819825
ISEQ_BODY(class_iseq)->local_table_size,
820826
ISEQ_BODY(class_iseq)->stack_max);

prism_compile.c

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,16 +1377,37 @@ pm_new_child_iseq(rb_iseq_t *iseq, pm_scope_node_t *node, VALUE name, const rb_i
13771377
return ret_iseq;
13781378
}
13791379

1380+
static int
1381+
pm_cpath_const_p(const pm_node_t *node)
1382+
{
1383+
switch (PM_NODE_TYPE(node)) {
1384+
case PM_CONSTANT_READ_NODE:
1385+
return TRUE;
1386+
case PM_CONSTANT_PATH_NODE:
1387+
{
1388+
const pm_node_t *parent = ((const pm_constant_path_node_t *) node)->parent;
1389+
if (!parent) return TRUE; /* ::Foo */
1390+
return pm_cpath_const_p(parent);
1391+
}
1392+
default:
1393+
return FALSE;
1394+
}
1395+
}
1396+
13801397
static int
13811398
pm_compile_class_path(rb_iseq_t *iseq, const pm_node_t *node, const pm_node_location_t *node_location, LINK_ANCHOR *const ret, bool popped, pm_scope_node_t *scope_node)
13821399
{
13831400
if (PM_NODE_TYPE_P(node, PM_CONSTANT_PATH_NODE)) {
13841401
const pm_node_t *parent = ((const pm_constant_path_node_t *) node)->parent;
13851402

13861403
if (parent) {
1387-
/* Bar::Foo */
1404+
/* Bar::Foo or expr::Foo */
13881405
PM_COMPILE(parent);
1389-
return VM_DEFINECLASS_FLAG_SCOPED;
1406+
int flags = VM_DEFINECLASS_FLAG_SCOPED;
1407+
if (!pm_cpath_const_p(parent)) {
1408+
flags |= VM_DEFINECLASS_FLAG_DYNAMIC_CREF;
1409+
}
1410+
return flags;
13901411
}
13911412
else {
13921413
/* toplevel class ::Foo */
@@ -10342,7 +10363,17 @@ pm_compile_node(rb_iseq_t *iseq, const pm_node_t *node, LINK_ANCHOR *const ret,
1034210363

1034310364
ID singletonclass;
1034410365
CONST_ID(singletonclass, "singletonclass");
10345-
PUSH_INSN3(ret, location, defineclass, ID2SYM(singletonclass), child_iseq, INT2FIX(VM_DEFINECLASS_TYPE_SINGLETON_CLASS));
10366+
10367+
/* `class << self` in a class body and `class << Foo` (constant
10368+
receiver) are stable. All other forms are potentially dynamic. */
10369+
int sclass_flags = VM_DEFINECLASS_TYPE_SINGLETON_CLASS;
10370+
if (!(PM_NODE_TYPE_P(cast->expression, PM_SELF_NODE) &&
10371+
ISEQ_BODY(iseq)->type == ISEQ_TYPE_CLASS) &&
10372+
!pm_cpath_const_p(cast->expression)) {
10373+
sclass_flags |= VM_DEFINECLASS_FLAG_DYNAMIC_CREF;
10374+
}
10375+
10376+
PUSH_INSN3(ret, location, defineclass, ID2SYM(singletonclass), child_iseq, INT2FIX(sclass_flags));
1034610377

1034710378
if (popped) PUSH_INSN(ret, location, pop);
1034810379
RB_OBJ_WRITTEN(iseq, Qundef, (VALUE) child_iseq);

test/ruby/test_class.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,29 @@ def xyzzy
733733
}
734734
end
735735

736+
def test_dynamic_module_cpath_constant_namespace # [Bug #20948]
737+
assert_separately([], <<~'RUBY')
738+
module M1
739+
module Foo
740+
X = 1
741+
end
742+
end
743+
744+
module M2
745+
module Foo
746+
X = 2
747+
end
748+
end
749+
750+
results = [M1, M2].map do
751+
module it::Foo
752+
X
753+
end
754+
end
755+
assert_equal([1, 2], results)
756+
RUBY
757+
end
758+
736759
def test_namescope_error_message
737760
m = Module.new
738761
o = m.module_eval "class A\u{3042}; self; end.new"

test/ruby/test_yjit.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ def get_foo
547547
end
548548

549549
def test_opt_getconstant_path_slowpath
550-
assert_compiles(<<~RUBY, exits: { opt_getconstant_path: 1 }, result: [42, 42, 1, 1], call_threshold: 2)
550+
assert_compiles(<<~RUBY, result: [42, 42, 1, 1], call_threshold: 2)
551551
class A
552552
FOO = 42
553553
class << self

vm_core.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,9 +1243,12 @@ typedef enum {
12431243
#define VM_DEFINECLASS_TYPE(x) ((rb_vm_defineclass_type_t)(x) & VM_DEFINECLASS_TYPE_MASK)
12441244
#define VM_DEFINECLASS_FLAG_SCOPED 0x08
12451245
#define VM_DEFINECLASS_FLAG_HAS_SUPERCLASS 0x10
1246+
#define VM_DEFINECLASS_FLAG_DYNAMIC_CREF 0x20
12461247
#define VM_DEFINECLASS_SCOPED_P(x) ((x) & VM_DEFINECLASS_FLAG_SCOPED)
12471248
#define VM_DEFINECLASS_HAS_SUPERCLASS_P(x) \
12481249
((x) & VM_DEFINECLASS_FLAG_HAS_SUPERCLASS)
1250+
#define VM_DEFINECLASS_DYNAMIC_CREF_P(x) \
1251+
((x) & VM_DEFINECLASS_FLAG_DYNAMIC_CREF)
12491252

12501253
/* iseq.c */
12511254
RUBY_SYMBOL_EXPORT_BEGIN

vm_insnhelper.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -989,14 +989,14 @@ vm_get_const_key_cref(const VALUE *ep)
989989
const rb_cref_t *key_cref = cref;
990990

991991
while (cref) {
992-
if (RCLASS_SINGLETON_P(CREF_CLASS(cref)) ||
993-
RCLASS_CLONED_P(CREF_CLASS(cref)) ) {
992+
if (CREF_DYNAMIC(cref) ||
993+
RCLASS_CLONED_P(CREF_CLASS(cref))) {
994994
return key_cref;
995995
}
996996
cref = CREF_NEXT(cref);
997997
}
998998

999-
/* does not include singleton class */
999+
/* no dynamic singleton class or cloned class found */
10001000
return NULL;
10011001
}
10021002

0 commit comments

Comments
 (0)