Skip to content

Commit 879ad25

Browse files
authored
Port most of branch_stub_hit() + two other minor fixes (ruby#208)
* Re-include some headers for bindgen These were included from yjit_codegen.c and now that it's pruned bindgen wasn't finding them anymore. * Port most of branch_stub_hit() * gen_single_block(): Use the starting context from the block Exposed now that branch_stub_hit() works. The input to this function is not super clear so not surprised that this wasn't ported over correctly.
1 parent d88c980 commit 879ad25

File tree

7 files changed

+158
-102
lines changed

7 files changed

+158
-102
lines changed

yjit.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "internal.h"
66
#include "internal/string.h"
7+
#include "internal/hash.h"
8+
#include "internal/variable.h"
79
#include "vm_core.h"
810
#include "vm_callinfo.h"
911
#include "builtin.h"
@@ -446,6 +448,25 @@ rb_get_cfp_sp(struct rb_control_frame_struct *cfp) {
446448
return cfp->sp;
447449
}
448450

451+
void
452+
rb_set_cfp_pc(struct rb_control_frame_struct *cfp, const VALUE *pc)
453+
{
454+
cfp->pc = pc;
455+
}
456+
457+
void
458+
rb_set_cfp_sp(struct rb_control_frame_struct *cfp, VALUE *sp)
459+
{
460+
cfp->sp = sp;
461+
}
462+
463+
rb_iseq_t *
464+
rb_cfp_get_iseq(struct rb_control_frame_struct *cfp)
465+
{
466+
// TODO(alan) could assert frame type here to make sure that it's a ruby frame with an iseq.
467+
return cfp->iseq;
468+
}
469+
449470
VALUE
450471
rb_get_cfp_self(struct rb_control_frame_struct *cfp) {
451472
return cfp->self;
@@ -496,6 +517,26 @@ rb_get_call_data_ci(struct rb_call_data* cd) {
496517
return cd->ci;
497518
}
498519

520+
const uint8_t *
521+
rb_yjit_branch_stub_hit(void *branch_ptr, uint32_t target_idx, rb_execution_context_t *ec)
522+
{
523+
const uint8_t *ret;
524+
// Acquire the VM lock and then signal all other Ruby threads (ractors) to
525+
// contend for the VM lock, putting them to sleep. YJIT uses this to evict
526+
// threads running inside generated code so among other things, it can
527+
// safely change memory protection of regions housing generated code.
528+
RB_VM_LOCK_ENTER();
529+
rb_vm_barrier();
530+
531+
const uint8_t *rb_yjit_rust_branch_stub_hit(void *branch_ptr, uint32_t target_idx, rb_execution_context_t *ec);
532+
ret = rb_yjit_rust_branch_stub_hit(branch_ptr, target_idx, ec);
533+
534+
// Release the VM lock. Note, watch out for Ruby exceptions and Rust panics
535+
// to ensure that the lock is properly released in exceptional situations.
536+
RB_VM_LOCK_LEAVE();
537+
538+
return ret;
539+
}
499540
#include "yjit_iface.c"
500541

501542
#endif // if JIT_ENABLED && PLATFORM_SUPPORTED_P

yjit/bindgen/src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ fn main() {
150150
// Opaque types from vm_core.h
151151
.blocklist_type("rb_execution_context_.*")
152152
.opaque_type("rb_execution_context_.*")
153+
.blocklist_type("rb_control_frame_struct")
154+
.opaque_type("rb_control_frame_struct")
153155

154156
// From yjit.c
155157
.allowlist_function("rb_iseq_(get|set)_yjit_payload")
@@ -160,6 +162,8 @@ fn main() {
160162
.allowlist_function("rb_yjit_get_page_size")
161163
.allowlist_function("rb_leaf_invokebuiltin_iseq_p")
162164
.allowlist_function("rb_leaf_builtin_function")
165+
.allowlist_function("rb_set_cfp_(pc|sp)")
166+
.allowlist_function("rb_cfp_get_iseq")
163167

164168
// Not sure why it's picking these up, but don't.
165169
.blocklist_type("FILE")

yjit/src/codegen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ pub fn gen_single_block(blockref: &BlockRef, ec: EcPtr, cb: &mut CodeBlock, ocb:
742742
let iseq_size = unsafe { get_iseq_encoded_size(iseq) };
743743
let mut insn_idx: c_uint = blockid.idx;
744744
let starting_insn_idx = insn_idx;
745-
let mut ctx = Context::new();
745+
let mut ctx = blockref.borrow().get_ctx();
746746

747747
// Initialize a JIT state object
748748
let mut jit = JITState::new(blockref);

yjit/src/core.rs

Lines changed: 85 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::options::*;
99
use crate::stats::*;
1010
use InsnOpnd::*;
1111
use TempMapping::*;
12+
use core::ffi::{c_void};
1213

1314
// Maximum number of temp value types we keep track of
1415
const MAX_TEMP_TYPES: usize = 8;
@@ -1219,132 +1220,142 @@ fn make_branch_entry(block: BlockRef, src_ctx: &Context, gen_fn: BranchGenFn) ->
12191220
// Called by the generated code when a branch stub is executed
12201221
// Triggers compilation of branches and code patching
12211222
#[no_mangle]
1222-
pub extern "C" fn branch_stub_hit(branch_ptr: *const u8, target_idx: u32, ec: EcPtr) -> *const u8
1223+
pub extern "C" fn rb_yjit_rust_branch_stub_hit(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) -> *const u8
12231224
{
1225+
assert!(!branch_ptr.is_null());
1226+
12241227
//branch_ptr is actually:
12251228
//branch_ptr: *const RefCell<Branch>
1229+
let branch_rc = unsafe { BranchRef::from_raw(branch_ptr as *const RefCell<Branch>) };
1230+
let mut branch = branch_rc.borrow_mut();
12261231

1227-
todo!();
1228-
1229-
// NOTE: here we need to take the VM lock. Should we call this function from C?
1232+
let branch_size_on_entry = branch.code_size();
12301233

1231-
/*
1232-
uint8_t *dst_addr = NULL;
1234+
let target_idx: usize = target_idx as usize;
1235+
let target = branch.targets[target_idx];
1236+
let target_ctx = branch.target_ctxs[target_idx];
12331237

1234-
// Stop other ractors since we are going to patch machine code.
1235-
// This is how the GC does it.
1236-
RB_VM_LOCK_ENTER();
1237-
rb_vm_barrier();
1238-
1239-
const ptrdiff_t branch_size_on_entry = branch_code_size(branch);
1238+
let target_branch_shape = match target_idx {
1239+
0 => BranchShape::Next0,
1240+
1 => BranchShape::Next1,
1241+
_ => unreachable!("target_idx < 2 must always hold")
1242+
};
12401243

1241-
RUBY_ASSERT(branch != NULL);
1242-
RUBY_ASSERT(target_idx < 2);
1243-
blockid_t target = branch->targets[target_idx];
1244-
const ctx_t *target_ctx = &branch->target_ctxs[target_idx];
1244+
let cb = CodegenGlobals::get_inline_cb();
1245+
let ocb = CodegenGlobals::get_outlined_cb();
12451246

12461247
// If this branch has already been patched, return the dst address
12471248
// Note: ractors can cause the same stub to be hit multiple times
1248-
if (branch->blocks[target_idx]) {
1249-
dst_addr = branch->dst_addrs[target_idx];
1249+
if let Some(_) = branch.blocks[target_idx] {
1250+
return branch.dst_addrs[target_idx].unwrap().raw_ptr();
12501251
}
1251-
else {
1252-
rb_vm_barrier();
1252+
1253+
let (cfp, original_interp_sp) = unsafe {
1254+
let cfp = get_ec_cfp(ec);
1255+
let original_interp_sp = get_cfp_sp(cfp);
1256+
1257+
let reconned_pc = rb_iseq_pc_at_idx(rb_cfp_get_iseq(cfp), target.idx);
1258+
let reconned_sp = original_interp_sp.add(target_ctx.sp_offset as usize);
1259+
1260+
// Update the PC in the current CFP, because it may be out of sync in JITted code
1261+
rb_set_cfp_pc(cfp, reconned_pc);
12531262

12541263
// :stub-sp-flush:
12551264
// Generated code do stack operations without modifying cfp->sp, while the
12561265
// cfp->sp tells the GC what values on the stack to root. Generated code
12571266
// generally takes care of updating cfp->sp when it calls runtime routines that
12581267
// could trigger GC, but it's inconvenient to do it before calling this function.
12591268
// So we do it here instead.
1260-
VALUE *const original_interp_sp = ec->cfp->sp;
1261-
ec->cfp->sp += target_ctx->sp_offset;
1269+
rb_set_cfp_sp(cfp, reconned_sp);
12621270

1263-
// Update the PC in the current CFP, because it
1264-
// may be out of sync in JITted code
1265-
ec->cfp->pc = yjit_iseq_pc_at_idx(target.iseq, target.idx);
1271+
(cfp, original_interp_sp)
1272+
};
12661273

1267-
// Try to find an existing compiled version of this block
1268-
block_t *p_block = find_block_version(target, target_ctx);
12691274

1270-
// If this block hasn't yet been compiled
1271-
if (!p_block) {
1272-
const uint8_t branch_old_shape = branch->shape;
1273-
bool branch_modified = false;
1275+
// Try to find an existing compiled version of this block
1276+
let mut block = find_block_version(target, &target_ctx);
12741277

1275-
// If the new block can be generated right after the branch (at cb->write_pos)
1276-
if (cb.get_write_ptr() == branch->end_addr) {
1277-
// This branch should be terminating its block
1278-
RUBY_ASSERT(branch->end_addr == branch->block->end_addr);
1278+
// If this block hasn't yet been compiled
1279+
if block.is_none() {
12791280

1280-
// Change the branch shape to indicate the target block will be placed next
1281-
branch->shape = (uint8_t)target_idx;
1281+
let branch_old_shape = branch.shape;
1282+
let mut branch_modified = false;
12821283

1283-
// Rewrite the branch with the new, potentially more compact shape
1284-
regenerate_branch(cb, branch);
1285-
branch_modified = true;
1284+
// If the new block can be generated right after the branch (at cb->write_pos)
1285+
if Some(cb.get_write_ptr()) == branch.end_addr {
1286+
let branch_end_addr = branch.end_addr.unwrap();
12861287

1287-
// Ensure that the branch terminates the codeblock just like
1288-
// before entering this if block. This drops bytes off the end
1289-
// in case we shrank the branch when regenerating.
1290-
cb_set_write_ptr(cb, branch->end_addr);
1291-
}
1288+
// This branch should be terminating its block
1289+
assert!(branch.end_addr == branch.block.borrow().end_addr);
12921290

1293-
// Compile the new block version
1294-
p_block = gen_block_series(target, target_ctx, ec);
1291+
// Change the branch shape to indicate the target block will be placed next
1292+
branch.shape = target_branch_shape;
12951293

1296-
if (!p_block && branch_modified) {
1297-
// We couldn't generate a new block for the branch, but we modified the branch.
1298-
// Restore the branch by regenerating it.
1299-
branch->shape = branch_old_shape;
1300-
regenerate_branch(cb, branch);
1301-
}
1294+
// Rewrite the branch with the new, potentially more compact shape
1295+
regenerate_branch(cb, &mut branch);
1296+
branch_modified = true;
1297+
1298+
// Ensure that the branch terminates the codeblock just like
1299+
// before entering this if block. This drops bytes off the end
1300+
// in case we shrank the branch when regenerating.
1301+
cb.set_write_ptr(branch_end_addr);
13021302
}
13031303

1304-
if (p_block) {
1304+
// Compile the new block version
1305+
block = gen_block_series(target, &target_ctx, ec, cb, ocb);
1306+
1307+
if block.is_none() && branch_modified {
1308+
// We couldn't generate a new block for the branch, but we modified the branch.
1309+
// Restore the branch by regenerating it.
1310+
branch.shape = branch_old_shape;
1311+
regenerate_branch(cb, &mut branch);
1312+
}
1313+
}
1314+
1315+
let dst_addr = match block {
1316+
Some(block_rc) => {
1317+
let mut block = block_rc.borrow_mut();
13051318
// Branch shape should reflect layout
1306-
RUBY_ASSERT(!(branch->shape == (uint8_t)target_idx && p_block->start_addr != branch->end_addr));
1319+
assert!(! (branch.shape == target_branch_shape && block.start_addr != branch.end_addr));
13071320

13081321
// Add this branch to the list of incoming branches for the target
1309-
rb_darray_append(&p_block->incoming, branch);
1322+
block.incoming.push(branch_rc.clone());
13101323

13111324
// Update the branch target address
1312-
dst_addr = p_block->start_addr;
1313-
branch->dst_addrs[target_idx] = dst_addr;
1325+
let dst_addr = block.start_addr;
1326+
branch.dst_addrs[target_idx] = dst_addr;
13141327

13151328
// Mark this branch target as patched (no longer a stub)
1316-
branch->blocks[target_idx] = p_block;
1329+
branch.blocks[target_idx] = Some(block_rc.clone());
13171330

13181331
// Rewrite the branch with the new jump target address
1319-
regenerate_branch(cb, branch);
1332+
regenerate_branch(cb, &mut branch);
13201333

13211334
// Restore interpreter sp, since the code hitting the stub expects the original.
1322-
ec->cfp->sp = original_interp_sp;
1335+
unsafe { rb_set_cfp_sp(cfp, original_interp_sp) };
1336+
1337+
block.start_addr.unwrap()
13231338
}
1324-
else {
1339+
None => {
13251340
// Failed to service the stub by generating a new block so now we
13261341
// need to exit to the interpreter at the stubbed location. We are
13271342
// intentionally *not* restoring original_interp_sp. At the time of
13281343
// writing, reconstructing interpreter state only involves setting
13291344
// cfp->sp and cfp->pc. We set both before trying to generate the
13301345
// block. All there is left to do to exit is to pop the native
13311346
// frame. We do that in code_for_exit_from_stub.
1332-
dst_addr = code_for_exit_from_stub;
1347+
todo!("code_for_exit_from_stub");
13331348
}
1349+
};
13341350

1335-
cb_mark_all_executable(ocb);
1336-
cb_mark_all_executable(cb);
1337-
}
1338-
1339-
const ptrdiff_t new_branch_size = branch_code_size(branch);
1340-
RUBY_ASSERT_ALWAYS(new_branch_size >= 0);
1341-
RUBY_ASSERT_ALWAYS(new_branch_size <= branch_size_on_entry && "branch stubs should not enlarge branches");
1351+
ocb.unwrap().mark_all_executable();
1352+
cb.mark_all_executable();
13421353

1343-
RB_VM_LOCK_LEAVE();
1354+
let new_branch_size = branch.code_size();
1355+
assert!(new_branch_size <= branch_size_on_entry, "branch stubs should not enlarge branches");
13441356

13451357
// Return a pointer to the compiled block version
1346-
return dst_addr;
1347-
*/
1358+
dst_addr.raw_ptr()
13481359
}
13491360

13501361
// Get a version or stub corresponding to a branch target
@@ -1384,7 +1395,7 @@ fn get_branch_target(
13841395
mov(ocb, C_ARG_REGS[2], REG_EC);
13851396
mov(ocb, C_ARG_REGS[1], uimm_opnd(target_idx as u64));
13861397
mov(ocb, C_ARG_REGS[0], const_ptr_opnd(branch_ptr as *const u8));
1387-
call_ptr(ocb, REG0, branch_stub_hit as *mut u8);
1398+
call_ptr(ocb, REG0, rb_yjit_branch_stub_hit as *mut u8);
13881399

13891400
// Jump to the address returned by the
13901401
// branch_stub_hit call

yjit/src/cruby.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@
8484

8585
use std::ffi::CString;
8686
use std::convert::From;
87-
use std::os::raw::{c_int, c_uint, c_long, c_char};
87+
use std::os::raw::{c_int, c_uint, c_long, c_char, c_void};
8888

8989
// We check that we can do this with the configure script and a couple of
9090
// static asserts. u64 and not usize to play nice with lowering to x86.
@@ -231,6 +231,8 @@ extern "C" {
231231
#[link_name = "rb_METHOD_ENTRY_VISI"]
232232
pub fn METHOD_ENTRY_VISI(me: * const rb_callable_method_entry_t) -> rb_method_visibility_t;
233233

234+
pub fn rb_yjit_branch_stub_hit(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) -> *const c_void;
235+
234236
pub fn rb_str_bytesize(str:VALUE) -> VALUE;
235237
}
236238

yjit/src/cruby_bindings.inc.rs

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,21 @@ extern "C" {
495495
extern "C" {
496496
pub fn rb_ec_str_resurrect(ec: *mut rb_execution_context_struct, str_: VALUE) -> VALUE;
497497
}
498+
extern "C" {
499+
pub fn rb_hash_new_with_size(size: st_index_t) -> VALUE;
500+
}
501+
extern "C" {
502+
pub fn rb_hash_resurrect(hash: VALUE) -> VALUE;
503+
}
504+
extern "C" {
505+
pub fn rb_obj_ensure_iv_index_mapping(obj: VALUE, id: ID) -> u32;
506+
}
507+
extern "C" {
508+
pub fn rb_gvar_get(arg1: ID) -> VALUE;
509+
}
510+
extern "C" {
511+
pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE;
512+
}
498513
#[repr(C)]
499514
#[derive(Debug, Copy, Clone)]
500515
pub struct rb_builtin_function {
@@ -541,6 +556,15 @@ extern "C" {
541556
extern "C" {
542557
pub fn rb_leaf_builtin_function(iseq: *const rb_iseq_t) -> *const rb_builtin_function;
543558
}
559+
extern "C" {
560+
pub fn rb_set_cfp_pc(cfp: *mut rb_control_frame_struct, pc: *const VALUE);
561+
}
562+
extern "C" {
563+
pub fn rb_set_cfp_sp(cfp: *mut rb_control_frame_struct, sp: *mut VALUE);
564+
}
565+
extern "C" {
566+
pub fn rb_cfp_get_iseq(cfp: *mut rb_control_frame_struct) -> *mut rb_iseq_t;
567+
}
544568
#[repr(C)]
545569
pub struct rb_iv_index_tbl_entry {
546570
pub index: u32,
@@ -553,18 +577,3 @@ pub struct rb_cvar_class_tbl_entry {
553577
pub global_cvar_state: rb_serial_t,
554578
pub class_value: VALUE,
555579
}
556-
extern "C" {
557-
pub fn rb_hash_new_with_size(size: st_index_t) -> VALUE;
558-
}
559-
extern "C" {
560-
pub fn rb_hash_resurrect(hash: VALUE) -> VALUE;
561-
}
562-
extern "C" {
563-
pub fn rb_obj_ensure_iv_index_mapping(obj: VALUE, id: ID) -> u32;
564-
}
565-
extern "C" {
566-
pub fn rb_gvar_get(arg1: ID) -> VALUE;
567-
}
568-
extern "C" {
569-
pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE;
570-
}

0 commit comments

Comments
 (0)