Skip to content

Commit 1701214

Browse files
authored
Merge pull request #197 from Shopify/add-set-ivar
Implement attr_set method calls
2 parents d82a3a9 + d0091e1 commit 1701214

File tree

4 files changed

+103
-115
lines changed

4 files changed

+103
-115
lines changed

bootstraptest/test_yjit.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
# Check that frozen objects are respected
2+
assert_equal 'great', %q{
3+
class Foo
4+
attr_accessor :bar
5+
def initialize
6+
@bar = 1
7+
freeze
8+
end
9+
end
10+
11+
foo = Foo.new
12+
13+
5.times do
14+
begin
15+
foo.bar = 2
16+
rescue FrozenError
17+
end
18+
end
19+
20+
foo.bar == 1 ? "great" : "NG"
21+
}
22+
123
# Check that global variable set works
224
assert_equal 'string', %q{
325
def foo

test/ruby/test_yjit.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,20 @@ def test_compile_tostring
117117
assert_no_exits('"i am a string #{true}"')
118118
end
119119

120+
def test_compile_attr_set
121+
assert_no_exits(<<~EORB)
122+
class Foo
123+
attr_accessor :bar
124+
end
125+
126+
foo = Foo.new
127+
foo.bar = 3
128+
foo.bar = 3
129+
foo.bar = 3
130+
foo.bar = 3
131+
EORB
132+
end
133+
120134
def test_compile_regexp
121135
assert_no_exits('/#{true}/')
122136
end

vm_insnhelper.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,28 @@ rb_vm_setinstancevariable(const rb_iseq_t *iseq, VALUE obj, ID id, VALUE val, IV
13691369
vm_setinstancevariable(iseq, obj, id, val, ic);
13701370
}
13711371

1372+
/* Set the instance variable +val+ on object +obj+ at the +index+.
1373+
* This function only works with T_OBJECT objects, so make sure
1374+
* +obj+ is of type T_OBJECT before using this function.
1375+
*/
1376+
VALUE
1377+
rb_vm_set_ivar_idx(VALUE obj, uint32_t index, VALUE val)
1378+
{
1379+
RUBY_ASSERT(RB_TYPE_P(obj, T_OBJECT));
1380+
1381+
rb_check_frozen_internal(obj);
1382+
1383+
VM_ASSERT(!rb_ractor_shareable_p(obj));
1384+
1385+
if (UNLIKELY(index >= ROBJECT_NUMIV(obj))) {
1386+
rb_init_iv_list(obj);
1387+
}
1388+
VALUE *ptr = ROBJECT_IVPTR(obj);
1389+
RB_OBJ_WRITE(obj, &ptr[index], val);
1390+
1391+
return val;
1392+
}
1393+
13721394
static VALUE
13731395
vm_throw_continue(const rb_execution_context_t *ec, VALUE err)
13741396
{

yjit_codegen.c

Lines changed: 45 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,118 +1446,56 @@ enum {
14461446
SEND_MAX_DEPTH = 5, // up to 5 different classes
14471447
};
14481448

1449-
/*
1450-
// Codegen for setting an instance variable.
1451-
// Preconditions:
1452-
// - receiver is in REG0
1453-
// - receiver has the same class as CLASS_OF(comptime_receiver)
1454-
// - no stack push or pops to ctx since the entry to the codegen of the instruction being compiled
1455-
static codegen_status_t
1456-
gen_set_ivar(jitstate_t *jit, ctx_t *ctx, const int max_chain_depth, VALUE comptime_receiver, ID ivar_name, insn_opnd_t reg0_opnd, uint8_t *side_exit)
1449+
static uint32_t
1450+
yjit_force_iv_index(VALUE comptime_receiver, VALUE klass, ID name)
14571451
{
1458-
VALUE comptime_val_klass = CLASS_OF(comptime_receiver);
1459-
const ctx_t starting_context = *ctx; // make a copy for use with jit_chain_guard
1460-
1461-
// If the class uses the default allocator, instances should all be T_OBJECT
1462-
// NOTE: This assumes nobody changes the allocator of the class after allocation.
1463-
// Eventually, we can encode whether an object is T_OBJECT or not
1464-
// inside object shapes.
1465-
if (rb_get_alloc_func(comptime_val_klass) != rb_class_allocate_instance) {
1466-
GEN_COUNTER_INC(cb, setivar_not_object);
1467-
return YJIT_CANT_COMPILE;
1468-
}
1469-
RUBY_ASSERT(BUILTIN_TYPE(comptime_receiver) == T_OBJECT); // because we checked the allocator
1470-
1471-
// ID for the name of the ivar
1472-
ID id = ivar_name;
1452+
ID id = name;
14731453
struct rb_iv_index_tbl_entry *ent;
14741454
struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
14751455

1476-
// Lookup index for the ivar the instruction loads
1477-
if (iv_index_tbl && rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent)) {
1478-
uint32_t ivar_index = ent->index;
1479-
1480-
val_type_t val_type = ctx_get_opnd_type(ctx, OPND_STACK(0));
1481-
x86opnd_t val_to_write = ctx_stack_opnd(ctx, 0);
1482-
mov(cb, REG1, val_to_write);
1483-
1484-
// Bail if the value to write is a heap object, because this needs a write barrier
1485-
if (!val_type.is_imm) {
1486-
ADD_COMMENT(cb, "guard value is immediate");
1487-
test(cb, REG1, imm_opnd(RUBY_IMMEDIATE_MASK));
1488-
jz_ptr(cb, COUNTED_EXIT(side_exit, setivar_val_heapobject));
1489-
ctx_upgrade_opnd_type(ctx, OPND_STACK(0), TYPE_IMM);
1490-
}
1491-
1492-
// Pop the value to write
1493-
ctx_stack_pop(ctx, 1);
1494-
1495-
// Bail if this object is frozen
1496-
ADD_COMMENT(cb, "guard self is not frozen");
1497-
x86opnd_t flags_opnd = member_opnd(REG0, struct RBasic, flags);
1498-
test(cb, flags_opnd, imm_opnd(RUBY_FL_FREEZE));
1499-
jnz_ptr(cb, COUNTED_EXIT(side_exit, setivar_frozen));
1500-
1501-
// Pop receiver if it's on the temp stack
1502-
if (!reg0_opnd.is_self) {
1503-
(void)ctx_stack_pop(ctx, 1);
1504-
}
1456+
// Make sure there is a mapping for this ivar in the index table
1457+
if (!iv_index_tbl || !rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent)) {
1458+
rb_ivar_set(comptime_receiver, id, Qundef);
1459+
iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
1460+
RUBY_ASSERT(iv_index_tbl);
1461+
// Redo the lookup
1462+
RUBY_ASSERT_ALWAYS(rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent));
1463+
}
15051464

1506-
// Compile time self is embedded and the ivar index lands within the object
1507-
if (RB_FL_TEST_RAW(comptime_receiver, ROBJECT_EMBED) && ivar_index < ROBJECT_EMBED_LEN_MAX) {
1508-
// See ROBJECT_IVPTR() from include/ruby/internal/core/robject.h
1465+
return ent->index;
1466+
}
15091467

1510-
// Guard that self is embedded
1511-
// TODO: BT and JC is shorter
1512-
ADD_COMMENT(cb, "guard embedded setivar");
1513-
test(cb, flags_opnd, imm_opnd(ROBJECT_EMBED));
1514-
jit_chain_guard(JCC_JZ, jit, &starting_context, max_chain_depth, side_exit);
1468+
VALUE rb_vm_set_ivar_idx(VALUE obj, uint32_t idx, VALUE val);
15151469

1516-
// Store the ivar on the object
1517-
x86opnd_t ivar_opnd = mem_opnd(64, REG0, offsetof(struct RObject, as.ary) + ivar_index * SIZEOF_VALUE);
1518-
mov(cb, ivar_opnd, REG1);
1470+
// Codegen for setting an instance variable.
1471+
// Preconditions:
1472+
// - receiver is in REG0
1473+
// - receiver has the same class as CLASS_OF(comptime_receiver)
1474+
// - no stack push or pops to ctx since the entry to the codegen of the instruction being compiled
1475+
static codegen_status_t
1476+
gen_set_ivar(jitstate_t *jit, ctx_t *ctx, VALUE recv, VALUE klass, ID ivar_name)
1477+
{
1478+
// Save the PC and SP because the callee may allocate
1479+
// Note that this modifies REG_SP, which is why we do it first
1480+
jit_prepare_routine_call(jit, ctx, REG0);
15191481

1520-
// Push the ivar on the stack
1521-
// For attr_writer we'll need to push the value on the stack
1522-
//x86opnd_t out_opnd = ctx_stack_push(ctx, TYPE_UNKNOWN);
1523-
}
1524-
else {
1525-
// Compile time value is *not* embeded.
1526-
1527-
// Guard that value is *not* embedded
1528-
// See ROBJECT_IVPTR() from include/ruby/internal/core/robject.h
1529-
ADD_COMMENT(cb, "guard extended setivar");
1530-
x86opnd_t flags_opnd = member_opnd(REG0, struct RBasic, flags);
1531-
test(cb, flags_opnd, imm_opnd(ROBJECT_EMBED));
1532-
jit_chain_guard(JCC_JNZ, jit, &starting_context, max_chain_depth, side_exit);
1533-
1534-
// check that the extended table is big enough
1535-
if (ivar_index >= ROBJECT_EMBED_LEN_MAX + 1) {
1536-
// Check that the slot is inside the extended table (num_slots > index)
1537-
ADD_COMMENT(cb, "check index in extended table");
1538-
x86opnd_t num_slots = mem_opnd(32, REG0, offsetof(struct RObject, as.heap.numiv));
1539-
cmp(cb, num_slots, imm_opnd(ivar_index));
1540-
jle_ptr(cb, COUNTED_EXIT(side_exit, setivar_idx_out_of_range));
1541-
}
1482+
// Get the operands from the stack
1483+
x86opnd_t val_opnd = ctx_stack_pop(ctx, 1);
1484+
x86opnd_t recv_opnd = ctx_stack_pop(ctx, 1);
15421485

1543-
// Get a pointer to the extended table
1544-
x86opnd_t tbl_opnd = mem_opnd(64, REG0, offsetof(struct RObject, as.heap.ivptr));
1545-
mov(cb, REG0, tbl_opnd);
1486+
uint32_t ivar_index = yjit_force_iv_index(recv, klass, ivar_name);
15461487

1547-
// Write the ivar to the extended table
1548-
x86opnd_t ivar_opnd = mem_opnd(64, REG0, sizeof(VALUE) * ivar_index);
1549-
mov(cb, ivar_opnd, REG1);
1550-
}
1488+
// Call rb_vm_set_ivar_idx with the receiver, the index of the ivar, and the value
1489+
mov(cb, C_ARG_REGS[0], recv_opnd);
1490+
mov(cb, C_ARG_REGS[1], imm_opnd(ivar_index));
1491+
mov(cb, C_ARG_REGS[2], val_opnd);
1492+
call_ptr(cb, REG0, (void *)rb_vm_set_ivar_idx);
15511493

1552-
// Jump to next instruction. This allows guard chains to share the same successor.
1553-
jit_jump_to_next_insn(jit, ctx);
1554-
return YJIT_END_BLOCK;
1555-
}
1494+
x86opnd_t out_opnd = ctx_stack_push(ctx, TYPE_UNKNOWN);
1495+
mov(cb, out_opnd, RAX);
15561496

1557-
GEN_COUNTER_INC(cb, setivar_name_not_mapped);
1558-
return YJIT_CANT_COMPILE;
1497+
return YJIT_KEEP_COMPILING;
15591498
}
1560-
*/
15611499

15621500
// Codegen for getting an instance variable.
15631501
// Preconditions:
@@ -1616,21 +1554,7 @@ gen_get_ivar(jitstate_t *jit, ctx_t *ctx, const int max_chain_depth, VALUE compt
16161554
jit_chain_guard(JCC_JNE, jit, &starting_context, max_chain_depth, side_exit);
16171555
*/
16181556

1619-
// ID for the name of the ivar
1620-
ID id = ivar_name;
1621-
struct rb_iv_index_tbl_entry *ent;
1622-
struct st_table *iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
1623-
1624-
// Make sure there is a mapping for this ivar in the index table
1625-
if (!iv_index_tbl || !rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent)) {
1626-
rb_ivar_set(comptime_receiver, id, Qundef);
1627-
iv_index_tbl = ROBJECT_IV_INDEX_TBL(comptime_receiver);
1628-
RUBY_ASSERT(iv_index_tbl);
1629-
// Redo the lookup
1630-
RUBY_ASSERT_ALWAYS(rb_iv_index_tbl_lookup(iv_index_tbl, id, &ent));
1631-
}
1632-
1633-
uint32_t ivar_index = ent->index;
1557+
uint32_t ivar_index = yjit_force_iv_index(comptime_receiver, CLASS_OF(comptime_receiver), ivar_name);
16341558

16351559
// Pop receiver if it's on the temp stack
16361560
if (!reg0_opnd.is_self) {
@@ -3564,7 +3488,13 @@ gen_send_general(jitstate_t *jit, ctx_t *ctx, struct rb_call_data *cd, rb_iseq_t
35643488
}
35653489
case VM_METHOD_TYPE_ATTRSET:
35663490
GEN_COUNTER_INC(cb, send_ivar_set_method);
3567-
return YJIT_CANT_COMPILE;
3491+
3492+
if (argc != 1 || !RB_TYPE_P(comptime_recv, T_OBJECT)) {
3493+
return YJIT_CANT_COMPILE;
3494+
} else {
3495+
ID ivar_name = cme->def->body.attr.id;
3496+
return gen_set_ivar(jit, ctx, comptime_recv, comptime_recv_klass, ivar_name);
3497+
}
35683498
case VM_METHOD_TYPE_BMETHOD:
35693499
GEN_COUNTER_INC(cb, send_bmethod);
35703500
return YJIT_CANT_COMPILE;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy