8000 Implement greedy versioning. Refactor versioning logic. (#10) · github/ruby@da3cd87 · GitHub
[go: up one dir, main page]

Skip to content

Commit da3cd87

Browse files
authored
Implement greedy versioning. Refactor versioning logic. (#10)
* Implement eager versioning. Refactor versioning logic. * Add --version-limit and --greedy-versioning command-line args
1 parent 07240e9 commit da3cd87

File tree

7 files changed

+90
-46
lines changed

7 files changed

+90
-46
lines changed

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,15 @@ The core of CRuby's interpreter logic is found in:
9696

9797
## Contributing
9898

99-
We welcome open source contributors. If you are interested in contributing to this project, please contact Maxime Chevalier [(@Love2Code) via twitter](https://twitter.com/Love2Code) or by email (maxime.chevalierboisvert@shopify.com). You can also feel free to open new issues to report bugs or just to ask questions. Suggestions on how to make this readme file more helpful for new contributors are most welcome.
99+
We welcome open source contributors. You should feel free to open new issues to report bugs or just to ask questions.
100+
Suggestions on how to make this readme file more helpful for new contributors are most welcome.
101+
102+
Bug fixes and bug reports are very valuable to us. If you find bugs in YJIT, it's very possible be that nobody has reported this bug before,
103+
or that we don't have a good reproduction for it, so please open an issue and provide some information about your configuration and a description of how you
104+
encountered the problem. If you are able to produce a small reproduction to help us track down the bug, that is very much appreciated as well.
105+
106+
If you would like to contribute a large patch to YJIT, we suggest opening an issue or a discussion on this repository so that
107+
we can have an active discussion. A common problem is that sometimes people submit large pull requests to open source projects
108+
without prior communication, and we have to reject them because the work they implemented does not fit within the design of the
109+
project. We want to save you time and frustration, so please reach out and we can have a productive discussion as to how
110+
you can contribute things we will want to merge into YJIT.

ruby.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,12 @@ setup_yjit_options(const char *s, struct rb_yjit_options *yjit_opt)
10341034
if (opt_match_arg(s, l, "call-threshold")) {
10351035
yjit_opt->call_threshold = atoi(s + 1);
10361036
}
1037+
else if (opt_match_arg(s, l, "version-limit")) {
1038+
yjit_opt->version_limit = atoi(s + 1);
1039+
}
1040+
else if (opt_match_noarg(s, l, "greedy-versioning")) {
1041+
yjit_opt->greedy_versioning = true;
1042+
}
10371043
else if (opt_match_noarg(s, l, "stats")) {
10381044
yjit_opt->gen_stats = true;
10391045
}

yjit.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,20 @@ typedef struct rb_iseq_struct rb_iseq_t;
3030
#endif
3131

3232
struct rb_yjit_options {
33+
// Enable compilation with YJIT
3334
bool yjit_enabled;
3435

3536
// Number of method calls after which to start generating code
3637
// Threshold==1 means compile on first execution
3738
unsigned call_threshold;
3839

40+
// Generate versions greedily until the limit is hit
41+
bool greedy_versioning;
42+
43+
// Maximum number of versions per block
44+
// 1 means always create generic versions
45+
unsigned version_limit;
46+
3947
// Capture and print out stats
4048
bool gen_stats;
4149
};

yjit_codegen.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,15 @@ jit_jump_to_next_insn(jitstate_t *jit, const ctx_t *current_context)
335335

336336
// Compile a sequence of bytecode instructions for a given basic block version
337337
void
338-
yjit_gen_block(ctx_t *ctx, block_t *block, rb_execution_context_t *ec)
338+
yjit_gen_block(block_t *block, rb_execution_context_t *ec)
339339
{
340340
RUBY_ASSERT(cb != NULL);
341341
RUBY_ASSERT(block != NULL);
342-
RUBY_ASSERT(!(block->blockid.idx == 0 && ctx->stack_size > 0));
342+
RUBY_ASSERT(!(block->blockid.idx == 0 && block->ctx.stack_size > 0));
343+
344+
// Copy the block's context to avoid mutating it
345+
ctx_t ctx_copy = block->ctx;
346+
ctx_t* ctx = &ctx_copy;
343347

344348
const rb_iseq_t *iseq = block->blockid.iseq;
345349
uint32_t insn_idx = block->blockid.idx;

yjit_codegen.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ typedef codegen_status_t (*codegen_fn)(jitstate_t* jit, ctx_t* ctx);
4040

4141
uint8_t* yjit_entry_prologue();
4242

43-
void yjit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec);
43+
void yjit_gen_block(block_t* block, rb_execution_context_t* ec);
4444

4545
void yjit_init_codegen(void);
4646

yjit_core.c

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
#include "vm_sync.h"
44
#include "builtin.h"
55

6+
#include "yjit.h"
67
#include "yjit_asm.h"
78
#include "yjit_utils.h"
89
#include "yjit_iface.h"
910
#include "yjit_core.h"
1011
#include "yjit_codegen.h"
1112

12-
// Maximum number of versions per block
13-
#define MAX_VERSIONS 4
13+
// Maximum number of specialized block versions per block
14+
// Zero means generic versions only
15+
#define MAX_VERSIONS 3
1416

1517
/*
1618
Get an operand for the adjusted stack pointer address
@@ -419,26 +421,57 @@ block_t* find_block_version(blockid_t blockid, const ctx_t* ctx)
419421
}
420422
}
421423

424+
// If greedy versioning is enabled
425+
if (rb_yjit_opts.greedy_versioning)
426+
{
427+
// If we're below the version limit, don't settle for an imperfect match
428+
if ((uint32_t)rb_darray_size(versions) + 1 < rb_yjit_opts.version_limit && best_diff > 0) {
429+
return NULL;
430+
}
431+
}
432+
422433
return best_version;
423434
}
424435

436+
// Produce a generic context when the block version limit is hit for a blockid
437+
// Note that this will mutate the ctx argument
438+
void limit_block_versions(blockid_t blockid, ctx_t* ctx)
439+
{
440+
// Guard chains implement limits separately, do nothing
441+
if (ctx->chain_depth > 0)
442+
return;
443+
444+
// If this block version we're about to add will hit the version limit
445+
if (get_num_versions(blockid) + 1 >= rb_yjit_opts.version_limit)
446+
{
447+
// Produce a generic context that stores no type information,
448+
// but still respects the stack_size and sp_offset constraints
449+
// This new context will then match all future requests.
450+
ctx_t generic_ctx = DEFAULT_CTX;
451+
generic_ctx.stack_size = ctx->stack_size;
452+
generic_ctx.sp_offset = ctx->sp_offset;
453+
454+
// Mutate the incoming context
455+
*ctx = generic_ctx;
456+
}
457+
}
458+
425459
// Compile a new block version immediately
426460
block_t* gen_block_version(blockid_t blockid, const ctx_t* start_ctx, rb_execution_context_t* ec)
427461
{
428-
// Copy the context to avoid mutating it
429-
ctx_t ctx_copy = *start_ctx;
430-
ctx_t* ctx = &ctx_copy;
431-
432462
// Allocate a new block version object
433-
block_t* first_block = calloc(1, sizeof(block_t));
434-
first_block->blockid = blockid;
435-
memcpy(&first_block->ctx, ctx, sizeof(ctx_t));
463+
block_t* block = calloc(1, sizeof(block_t));
464+
block->blockid = blockid;
465+
memcpy(&block->ctx, start_ctx, sizeof(ctx_t));
436466

437-
// Block that is currently being compiled
438-
block_t* block = first_block;
467+
// Store a pointer to the first block (returned by this function)
468+
block_t* first_block = block;
469+
470+
// Limit the number of specialized versions for this block
471+
limit_block_versions(block->blockid, &block->ctx);
439472

440473
// Generate code for the first block
441-
yjit_gen_block(ctx, block, ec);
474+
yjit_gen_block(block, ec);
442475

443476
// Keep track of the new block version
444477
add_block_version(block->blockid, block);
@@ -462,16 +495,18 @@ block_t* gen_block_version(blockid_t blockid, const ctx_t* start_ctx, rb_executi
462495
rb_bug("invalid target for last branch");
463496
}
464497

465-
// Use the context from the branch
466-
*ctx = last_branch->target_ctxs[0];
467-
468498
// Allocate a new block version object
499+
// Use the context from the branch
469500
block = calloc(1, sizeof(block_t));
470501
block->blockid = last_branch->targets[0];
471-
memcpy(&block->ctx, ctx, sizeof(ctx_t));
502+
block->ctx = last_branch->target_ctxs[0];
503+
//memcpy(&block->ctx, ctx, sizeof(ctx_t));
504+
505+
// Limit the number of specialized versions for this block
506+
limit_block_versions(block->blockid, &block->ctx);
472507

473508
// Generate code for the current block
474-
yjit_gen_block(ctx, block, ec);
509+
yjit_gen_block(block, ec);
475510

476511
// Keep track of the new block version
477512
add_block_version(block->blockid, block);
@@ -514,7 +549,6 @@ static uint8_t *
514549
branch_stub_hit(branch_t* branch, const uint32_t target_idx, rb_execution_context_t* ec)
515550
{
516551
uint8_t* dst_addr;
517-
ctx_t generic_ctx;
518552

519553
// Stop other ractors since we are going to patch machine code.
520554
// This is how the GC does it.
@@ -555,17 +589,6 @@ branch_stub_hit(branch_t* branch, const uint32_t target_idx, rb_execution_contex
555589

556590
// If this block hasn't yet been compiled
557591
if (!p_block) {
558-
// Limit the number of block versions
559-
if (target_ctx->chain_depth == 0) { // guard chains implement limits individually
560-
if (get_num_versions(target) >= MAX_VERSIONS - 1) {
561-
//fprintf(stderr, "version limit hit in branch_stub_hit\n");
562-
generic_ctx = DEFAULT_CTX;
563-
generic_ctx.stack_size = target_ctx->stack_size;
564-
generic_ctx.sp_offset = target_ctx->sp_offset;
565-
target_ctx = &generic_ctx;
566-
}
567-
}
568-
569592
// If the new block can be generated right after the branch (at cb->write_pos)
570593
if (cb->write_pos == branch->end_pos) {
571594
// This branch should be terminating its block
@@ -720,7 +743,6 @@ void gen_direct_jump(
720743
)
721744
{
722745
RUBY_ASSERT(target0.iseq != NULL);
723-
ctx_t generic_ctx;
724746

725747
branch_t* branch = make_branch_entry(block, ctx, gen_jump_branch);
726748
branch->targets[0] = target0;
@@ -744,18 +766,8 @@ void gen_direct_jump(
744766
}
745767
else
746768
{
747-
// Limit the number of block versions
748-
if (get_num_versions(target0) >= MAX_VERSIONS - 1)
749-
{
750-
//fprintf(stderr, "version limit hit in gen_direct_jump\n");
751-
generic_ctx = DEFAULT_CTX;
752-
generic_ctx.stack_size = ctx->stack_size;
753-
generic_ctx.sp_offset = ctx->sp_offset;
754-
ctx = &generic_ctx;
755-
}
756-
757-
// The target block will be compiled next
758-
// It will be compiled in gen_block_version()
769+
// The target block will be compiled right after this one (fallthrough)
770+
// See the loop in gen_block_version()
759771
branch->dst_addrs[0] = NULL;
760772
branch->shape = SHAPE_NEXT0;
761773
branch->start_pos = cb->write_pos;

yjit_iface.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,9 @@ rb_yjit_init(struct rb_yjit_options *options)
10441044
if (rb_yjit_opts.call_threshold < 1) {
10451045
rb_yjit_opts.call_threshold = 2;
10461046
}
1047+
if (rb_yjit_opts.version_limit < 1) {
1048+
rb_yjit_opts.version_limit = 4;
1049+
}
10471050

10481051
blocks_assuming_stable_global_constant_state = st_init_numtable();
10491052
blocks_assuming_single_ractor_mode = st_init_numtable();

0 commit comments

Comments
 (0)
0