Skip to content

Commit 1b8eab8

Browse files
authored
Merge pull request ruby#47 from Shopify/ujit-auto-update
Mark and update object references in generated code
2 parents 362b478 + d336f2a commit 1b8eab8

5 files changed

Lines changed: 91 additions & 18 deletions

File tree

bootstraptest/test_ujit.rb

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ def foo
168168
end
169169
170170
new.foo
171-
UJIT.install_entry(RubyVM::InstructionSequence.of(instance_method(:foo)))
171+
new.foo
172+
new.foo
172173
new.foo
173174
end
174175
@@ -199,18 +200,36 @@ def read
199200
}
200201

201202
# Test that opt_aref checks the class of the receiver
202-
assert_equal ":special\n", %q{
203+
assert_equal 'special', %q{
203204
def foo(array)
204205
array[30]
205206
end
206207
207-
UJIT.install_entry(RubyVM::InstructionSequence.of(method(:foo)))
208+
foo([])
209+
foo([])
208210
209211
special = []
210212
def special.[](idx)
211-
:special
213+
'special'
214+
end
215+
216+
foo(special)
217+
}
218+
219+
# Test that object references in generated code get marked and moved
220+
assert_equal "good", %q{
221+
def bar
222+
"good"
212223
end
213224
214-
p foo(special)
215-
nil
225+
def foo
226+
bar
227+
end
228+
229+
foo
230+
foo
231+
232+
GC.verify_compaction_references(double_heap: true, toward: :empty)
233+
234+
foo
216235
}

ujit_codegen.c

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ jit_get_arg(jitstate_t* jit, size_t arg_idx)
5959
return *(jit->pc + arg_idx + 1);
6060
}
6161

62+
// Load a pointer to a GC'd object into a register and keep track of the reference
63+
static void
64+
jit_mov_gc_ptr(jitstate_t* jit, codeblock_t* cb, x86opnd_t reg, VALUE ptr)
65+
{
66+
RUBY_ASSERT(reg.type == OPND_REG && reg.num_bits == 64);
67+
RUBY_ASSERT(!SPECIAL_CONST_P(ptr));
68+
69+
mov(cb, reg, const_ptr_opnd((void*)ptr));
70+
// The pointer immediate is encoded as the last part of the mov written out.
71+
uint32_t ptr_offset = cb->write_pos - sizeof(VALUE);
72+
73+
if (!rb_darray_append(&jit->block->gc_object_offsets, ptr_offset)) {
74+
rb_bug("allocation failed");
75+
}
76+
}
77+
6278
/**
6379
Generate an inline exit to return to the interpreter
6480
*/
@@ -1083,7 +1099,7 @@ gen_oswb_cfunc(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_c
10831099
assume_method_lookup_stable(cd->cc, cme, jit->block);
10841100

10851101
// Bail if receiver class is different from compile-time call cache class
1086-
mov(cb, REG1, imm_opnd(cd->cc->klass));
1102+
jit_mov_gc_ptr(jit, cb, REG1, (VALUE)cd->cc->klass);
10871103
cmp(cb, klass_opnd, REG1);
10881104
jne_ptr(cb, side_exit);
10891105

@@ -1107,7 +1123,7 @@ gen_oswb_cfunc(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_c
11071123

11081124
// Put compile time cme into REG1. It's assumed to be valid because we are notified when
11091125
// any cme we depend on become outdated. See rb_ujit_method_lookup_change().
1110-
mov(cb, REG1, const_ptr_opnd(cme));
1126+
jit_mov_gc_ptr(jit, cb, REG1, (VALUE)cme);
11111127
// Write method entry at sp[-3]
11121128
// sp[-3] = me;
11131129
mov(cb, mem_opnd(64, REG0, 8 * -3), REG1);
@@ -1161,9 +1177,9 @@ gen_oswb_cfunc(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_c
11611177

11621178
// Call check_cfunc_dispatch
11631179
mov(cb, RDI, recv);
1164-
mov(cb, RSI, const_ptr_opnd(cd));
1180+
jit_mov_gc_ptr(jit, cb, RSI, (VALUE)cd);
11651181
mov(cb, RDX, const_ptr_opnd((void *)cfunc->func));
1166-
mov(cb, RCX, const_ptr_opnd(cme));
1182+
jit_mov_gc_ptr(jit, cb, RCX, (VALUE)cme);
11671183
call_ptr(cb, REG0, (void *)&check_cfunc_dispatch);
11681184

11691185
// Restore registers
@@ -1242,7 +1258,7 @@ gen_oswb_cfunc(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_c
12421258

12431259
bool rb_simple_iseq_p(const rb_iseq_t *iseq);
12441260

1245-
void
1261+
static void
12461262
gen_return_branch(codeblock_t* cb, uint8_t* target0, uint8_t* target1, uint8_t shape)
12471263
{
12481264
switch (shape)
@@ -1315,7 +1331,7 @@ gen_oswb_iseq(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_ca
13151331
assume_method_lookup_stable(cd->cc, cme, jit->block);
13161332

13171333
// Bail if receiver class is different from compile-time call cache class
1318-
mov(cb, REG1, imm_opnd(cd->cc->klass));
1334+
jit_mov_gc_ptr(jit, cb, REG1, (VALUE)cd->cc->klass);
13191335
cmp(cb, klass_opnd, REG1);
13201336
jne_ptr(cb, side_exit);
13211337

@@ -1343,7 +1359,7 @@ gen_oswb_iseq(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_ca
13431359

13441360
// Put compile time cme into REG1. It's assumed to be valid because we are notified when
13451361
// any cme we depend on become outdated. See rb_ujit_method_lookup_change().
1346-
mov(cb, REG1, const_ptr_opnd(cme));
1362+
jit_mov_gc_ptr(jit, cb, REG1, (VALUE)cme);
13471363
// Write method entry at sp[-3]
13481364
// sp[-3] = me;
13491365
mov(cb, mem_opnd(64, REG0, 8 * -3), REG1);
@@ -1378,7 +1394,7 @@ gen_oswb_iseq(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_ca
13781394
mov(cb, member_opnd(REG_CFP, rb_control_frame_t, ep), REG0);
13791395
mov(cb, REG0, recv);
13801396
mov(cb, member_opnd(REG_CFP, rb_control_frame_t, self), REG0);
1381-
mov(cb, REG0, const_ptr_opnd(iseq));
1397+
jit_mov_gc_ptr(jit, cb, REG0, (VALUE)iseq);
13821398
mov(cb, member_opnd(REG_CFP, rb_control_frame_t, iseq), REG0);
13831399
mov(cb, REG0, const_ptr_opnd(start_pc));
13841400
mov(cb, member_opnd(REG_CFP, rb_control_frame_t, pc), REG0);

ujit_core.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ get_first_version(const rb_iseq_t *iseq, unsigned idx)
155155
return rb_darray_get(body->ujit_blocks, idx);
156156
}
157157

158-
// Add a block version to the map. Block should be fully constructed
158+
// Keep track of a block version. Block should be fully constructed.
159159
static void
160160
add_block_version(blockid_t blockid, block_t* block)
161161
{
@@ -195,6 +195,17 @@ add_block_version(blockid_t blockid, block_t* block)
195195
RB_OBJ_WRITTEN(iseq, Qundef, block->dependencies.iseq);
196196
RB_OBJ_WRITTEN(iseq, Qundef, block->dependencies.cc);
197197
RB_OBJ_WRITTEN(iseq, Qundef, block->dependencies.cme);
198+
199+
// Run write barrier for all objects in generated code.
200+
uint32_t *offset_element;
201+
rb_darray_foreach(block->gc_object_offsets, offset_idx, offset_element) {
202+
uint32_t offset_to_value = *offset_element;
203+
uint8_t *value_address = cb_get_ptr(cb, offset_to_value);
204+
205+
VALUE object;
206+
memcpy(&object, value_address, SIZEOF_VALUE);
207+
RB_OBJ_WRITTEN(iseq, Qundef, object);
208+
}
198209
}
199210
}
200211

@@ -589,6 +600,7 @@ ujit_free_block(block_t *block)
589600
ujit_unlink_method_lookup_dependency(block);
590601
rb_darray_free(block->incoming);
591602
free(block);
603+
rb_darray_free(block->gc_object_offsets);
592604
}
593605

594606
// Invalidate one specific block version

ujit_core.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,12 @@ typedef struct ujit_block_version
123123
// List of incoming branches indices
124124
int32_array_t incoming;
125125

126+
// Offsets for GC managed objects in the mainline code block
127+
int32_array_t gc_object_offsets;
128+
126129
// Next block version for this blockid (singly-linked list)
127130
struct ujit_block_version *next;
128131

129-
// List node for all block versions in an iseq
130-
struct list_node iseq_block_node;
131-
132132
// GC managed objects that this block depend on
133133
struct {
134134
VALUE cc;

ujit_iface.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,17 @@ rb_ujit_iseq_mark(const struct rb_iseq_constant_body *body)
566566
rb_gc_mark_movable(block->dependencies.cc);
567567
rb_gc_mark_movable(block->dependencies.cme);
568568
rb_gc_mark_movable(block->dependencies.iseq);
569+
570+
// Walk over references to objects in generated code.
571+
uint32_t *offset_element;
572+
rb_darray_foreach(block->gc_object_offsets, offset_idx, offset_element) {
573+
uint32_t offset_to_value = *offset_element;
574+
uint8_t *value_address = cb_get_ptr(cb, offset_to_value);
575+
576+
VALUE object;
577+
memcpy(&object, value_address, SIZEOF_VALUE);
578+
rb_gc_mark_movable(object);
579+
}
569580
}
570581
}
571582
}
@@ -581,6 +592,21 @@ rb_ujit_iseq_update_references(const struct rb_iseq_constant_body *body)
581592
block->dependencies.cc = rb_gc_location(block->dependencies.cc);
582593
block->dependencies.cme = rb_gc_location(block->dependencies.cme);
583594
block->dependencies.iseq = rb_gc_location(block->dependencies.iseq);
595+
596+
// Walk over references to objects in generated code.
597+
uint32_t *offset_element;
598+
rb_darray_foreach(block->gc_object_offsets, offset_idx, offset_element) {
599+
uint32_t offset_to_value = *offset_element;
600+
uint8_t *value_address = cb_get_ptr(cb, offset_to_value);
601+
602+
VALUE object;
603+
memcpy(&object, value_address, SIZEOF_VALUE);
604+
VALUE possibly_moved = rb_gc_location(object);
605+
// Only write when the VALUE moves, to be CoW friendly.
606+
if (possibly_moved != object) {
607+
memcpy(value_address, &possibly_moved, SIZEOF_VALUE);
608+
}
609+
}
584610
}
585611
}
586612
}

0 commit comments

Comments
 (0)