Skip to content

Commit b74d656

Browse files
committed
Extract yjit_force_iv_index and make it work when object is frozen
In an effort to simplify the logic YJIT generates for accessing instance variable, YJIT ensures that a given name-to-index mapping exists at compile time. In the case that the mapping doesn't exist, it was created by using rb_ivar_set() with Qundef on the sample object we see at compile time. This hack isn't fine if the sample object happens to be frozen, in which case YJIT would raise a FrozenError unexpectedly. To deal with this, make a new function that only reserves the mapping but doesn't touch the object. This is rb_obj_ensure_iv_index_mapping(). This new function superceeds the functionality of rb_iv_index_tbl_lookup() so it was removed. Reported by and includes a test case from John Hawthorn <john@hawthorn.email> Fixes: GH-282
1 parent 2e24305 commit b74d656

File tree

5 files changed

+51
-32
lines changed

5 files changed

+51
-32
lines changed

bootstraptest/test_yjit.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2160,3 +2160,18 @@ def default_expression(value: default_value)
21602160
21612161
5.times.map { default_expression(value: 2) }.uniq
21622162
}
2163+
2164+
# attr_reader on frozen object
2165+
assert_equal 'false', %q{
2166+
class Foo
2167+
attr_reader :exception
2168+
2169+
def failed?
2170+
!exception.nil?
2171+
end
2172+
end
2173+
2174+
foo = Foo.new.freeze
2175+
foo.failed?
2176+
foo.failed?
2177+
}

internal/variable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ void rb_gvar_ractor_local(const char *name);
3535
static inline bool ROBJ_TRANSIENT_P(VALUE obj);
3636
static inline void ROBJ_TRANSIENT_SET(VALUE obj);
3737
static inline void ROBJ_TRANSIENT_UNSET(VALUE obj);
38+
uint32_t rb_obj_ensure_iv_index_mapping(VALUE obj, ID id);
3839

3940
RUBY_SYMBOL_EXPORT_BEGIN
4041
/* variable.c (export) */

variable.c

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,12 +1446,13 @@ rb_init_iv_list(VALUE obj)
14461446
init_iv_list(obj, len, newsize, index_tbl);
14471447
}
14481448

1449-
static VALUE
1450-
obj_ivar_set(VALUE obj, ID id, VALUE val)
1449+
// Retreive or create the id-to-index mapping for a given object and an
1450+
// instance variable name.
1451+
static struct ivar_update
1452+
obj_ensure_iv_index_mapping(VALUE obj, ID id)
14511453
{
14521454
VALUE klass = rb_obj_class(obj);
14531455
struct ivar_update ivup;
1454-
uint32_t len;
14551456
ivup.iv_extended = 0;
14561457
ivup.u.iv_index_tbl = iv_index_tbl_make(obj, klass);
14571458

@@ -1461,6 +1462,32 @@ obj_ivar_set(VALUE obj, ID id, VALUE val)
14611462
}
14621463
RB_VM_LOCK_LEAVE();
14631464

1465+
return ivup;
1466+
}
1467+
1468+
// Return the instance variable index for a given name and T_OBJECT object. The
1469+
// mapping between name and index lives on `rb_obj_class(obj)` and is created
1470+
// if not already present.
1471+
//
1472+
// @note May raise when there are too many instance variables.
1473+
// @note YJIT uses this function at compile time to simplify the work needed to
1474+
// access the variable at runtime.
1475+
uint32_t
1476+
rb_obj_ensure_iv_index_mapping(VALUE obj, ID id)
1477+
{
1478+
RUBY_ASSERT(RB_TYPE_P(obj, T_OBJECT));
1479+
// This uint32_t cast shouldn't lose information as it's checked in
1480+
// iv_index_tbl_extend(). The index is stored as an uint32_t in
1481+
// struct rb_iv_index_tbl_entry.
1482+
return (uint32_t)obj_ensure_iv_index_mapping(obj, id).index;
1483+
}
1484+
1485+
static VALUE
1486+
obj_ivar_set(VALUE obj, ID id, VALUE val)
1487+
{
1488+
uint32_t len;
1489+
struct ivar_update ivup = obj_ensure_iv_index_mapping(obj, id);
1490+
14641491
len = ROBJECT_NUMIV(obj);
14651492
if (len <= ivup.index) {
14661493
uint32_t newsize = iv_index_tbl_newsize(&ivup);

vm_insnhelper.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,12 +1105,6 @@ iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_index_tbl
11051105
return found ? true : false;
11061106
}
11071107

1108-
bool
1109-
rb_iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_index_tbl_entry **ent)
1110-
{
1111-
return iv_index_tbl_lookup(iv_index_tbl, id, ent);
1112-
}
1113-
11141108
ALWAYS_INLINE(static void fill_ivar_cache(const rb_iseq_t *iseq, IVC ic, const struct rb_callcache *cc, int is_attr, struct rb_iv_index_tbl_entry *ent));
11151109

11161110
static inline void

yjit_codegen.c

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,33 +1478,12 @@ jit_chain_guard(enum jcc_kinds jcc, jitstate_t *jit, const ctx_t *ctx, uint8_t d
14781478
}
14791479
}
14801480

1481-
bool rb_iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_index_tbl_entry **ent); // vm_insnhelper.c
1482-
14831481
enum {
14841482
GETIVAR_MAX_DEPTH = 10, // up to 5 different classes, and embedded or not for each
14851483
OPT_AREF_MAX_CHAIN_DEPTH = 2, // hashes and arrays
14861484
SEND_MAX_DEPTH = 5, // up to 5 different classes
14871485
};
14881486

1489-
static uint32_t
1490-
yjit_force_iv_index(VALUE comptime_receiver, VALUE klass, ID name)
1491-
{
1492-
ID id = name;
1493-
struct rb_iv_index_tbl_entry *ent;
1494-
struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
1495-
1496-
// Make sure there is a mapping for this ivar in the index table
1497-
if (!iv_index_tbl || !rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent)) {
1498-
rb_ivar_set(comptime_receiver, id, Qundef);
1499-
iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
1500-
RUBY_ASSERT(iv_index_tbl);
1501-
// Redo the lookup
1502-
RUBY_ASSERT_ALWAYS(rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent));
1503-
}
1504-
1505-
return ent->index;
1506-
}
1507-
15081487
VALUE rb_vm_set_ivar_idx(VALUE obj, uint32_t idx, VALUE val);
15091488

15101489
// Codegen for setting an instance variable.
@@ -1523,7 +1502,7 @@ gen_set_ivar(jitstate_t *jit, ctx_t *ctx, VALUE recv, VALUE klass, ID ivar_name)
15231502
x86opnd_t val_opnd = ctx_stack_pop(ctx, 1);
15241503
x86opnd_t recv_opnd = ctx_stack_pop(ctx, 1);
15251504

1526-
uint32_t ivar_index = yjit_force_iv_index(recv, klass, ivar_name);
1505+
uint32_t ivar_index = rb_obj_ensure_iv_index_mapping(recv, ivar_name);
15271506

15281507
// Call rb_vm_set_ivar_idx with the receiver, the index of the ivar, and the value
15291508
mov(cb, C_ARG_REGS[0], recv_opnd);
@@ -1596,7 +1575,10 @@ gen_get_ivar(jitstate_t *jit, ctx_t *ctx, const int max_chain_depth, VALUE compt
15961575
jit_chain_guard(JCC_JNE, jit, &starting_context, max_chain_depth, side_exit);
15971576
*/
15981577

1599-
uint32_t ivar_index = yjit_force_iv_index(comptime_receiver, CLASS_OF(comptime_receiver), ivar_name);
1578+
// FIXME: Mapping the index could fail when there is too many ivar names. If we're
1579+
// compiling for a branch stub that can cause the exception to be thrown from the
1580+
// wrong PC.
1581+
uint32_t ivar_index = rb_obj_ensure_iv_index_mapping(comptime_receiver, ivar_name);
16001582

16011583
// Pop receiver if it's on the temp stack
16021584
if (!reg0_opnd.is_self) {

0 commit comments

Comments
 (0)