Skip to content

Commit

Permalink
Extract yjit_force_iv_index and make it work when object is frozen
Browse files Browse the repository at this point in the history
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
  • Loading branch information
XrXr committed Oct 20, 2021
1 parent bead4ed commit b3fe3df
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 32 deletions.
15 changes: 15 additions & 0 deletions bootstraptest/test_yjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2160,3 +2160,18 @@ def default_expression(value: default_value)
5.times.map { default_expression(value: 2) }.uniq
}

# attr_reader on frozen object
assert_equal 'false', %q{
class Foo
attr_reader :exception
def failed?
!exception.nil?
end
end
foo = Foo.new.freeze
foo.failed?
foo.failed?
}
1 change: 1 addition & 0 deletions internal/variable.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ void rb_gvar_ractor_local(const char *name);
static inline bool ROBJ_TRANSIENT_P(VALUE obj);
static inline void ROBJ_TRANSIENT_SET(VALUE obj);
static inline void ROBJ_TRANSIENT_UNSET(VALUE obj);
uint32_t rb_obj_ensure_iv_index_mapping(VALUE obj, ID id);

RUBY_SYMBOL_EXPORT_BEGIN
/* variable.c (export) */
Expand Down
33 changes: 30 additions & 3 deletions variable.c
Original file line number Diff line number Diff line change
Expand Up @@ -1446,12 +1446,13 @@ rb_init_iv_list(VALUE obj)
init_iv_list(obj, len, newsize, index_tbl);
}

static VALUE
obj_ivar_set(VALUE obj, ID id, VALUE val)
// Retreive or create the id-to-index mapping for a given object and an
// instance variable name.
static struct ivar_update
obj_ensure_iv_index_mapping(VALUE obj, ID id)
{
VALUE klass = rb_obj_class(obj);
struct ivar_update ivup;
uint32_t len;
ivup.iv_extended = 0;
ivup.u.iv_index_tbl = iv_index_tbl_make(obj, klass);

Expand All @@ -1461,6 +1462,32 @@ obj_ivar_set(VALUE obj, ID id, VALUE val)
}
RB_VM_LOCK_LEAVE();

return ivup;
}

// Return the instance variable index for a given name and T_OBJECT object. The
// mapping between name and index lives on `rb_obj_class(obj)` and is created
// if not already present.
//
// @note May raise when there are too many instance variables.
// @note YJIT uses this function at compile time to simplify the work needed to
// access the variable at runtime.
uint32_t
rb_obj_ensure_iv_index_mapping(VALUE obj, ID id)
{
RUBY_ASSERT(RB_TYPE_P(obj, T_OBJECT));
// This uint32_t cast shouldn't lose information as it's checked in
// iv_index_tbl_extend(). The index is stored as an uint32_t in
// struct rb_iv_index_tbl_entry.
return (uint32_t)obj_ensure_iv_index_mapping(obj, id).index;
}

static VALUE
obj_ivar_set(VALUE obj, ID id, VALUE val)
{
uint32_t len;
struct ivar_update ivup = obj_ensure_iv_index_mapping(obj, id);

len = ROBJECT_NUMIV(obj);
if (len <= ivup.index) {
uint32_t newsize = iv_index_tbl_newsize(&ivup);
Expand Down
6 changes: 0 additions & 6 deletions vm_insnhelper.c
Original file line number Diff line number Diff line change
Expand Up @@ -1105,12 +1105,6 @@ iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_index_tbl
return found ? true : false;
}

bool
rb_iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_index_tbl_entry **ent)
{
return iv_index_tbl_lookup(iv_index_tbl, id, ent);
}

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));

static inline void
Expand Down
28 changes: 5 additions & 23 deletions yjit_codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -1478,33 +1478,12 @@ jit_chain_guard(enum jcc_kinds jcc, jitstate_t *jit, const ctx_t *ctx, uint8_t d
}
}

bool rb_iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_index_tbl_entry **ent); // vm_insnhelper.c

enum {
GETIVAR_MAX_DEPTH = 10, // up to 5 different classes, and embedded or not for each
OPT_AREF_MAX_CHAIN_DEPTH = 2, // hashes and arrays
SEND_MAX_DEPTH = 5, // up to 5 different classes
};

static uint32_t
yjit_force_iv_index(VALUE comptime_receiver, VALUE klass, ID name)
{
ID id = name;
struct rb_iv_index_tbl_entry *ent;
struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);

// Make sure there is a mapping for this ivar in the index table
if (!iv_index_tbl || !rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent)) {
rb_ivar_set(comptime_receiver, id, Qundef);
iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
RUBY_ASSERT(iv_index_tbl);
// Redo the lookup
RUBY_ASSERT_ALWAYS(rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent));
}

return ent->index;
}

VALUE rb_vm_set_ivar_idx(VALUE obj, uint32_t idx, VALUE val);

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

uint32_t ivar_index = yjit_force_iv_index(recv, klass, ivar_name);
uint32_t ivar_index = rb_obj_ensure_iv_index_mapping(recv, ivar_name);

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

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

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

0 comments on commit b3fe3df

Please sign in to comment.