Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -1344,11 +1344,11 @@ $(BUILTIN_BINARY:no=builtin)_binary.rbbin:

$(BUILTIN_RB_INCS): $(tooldir)/mk_builtin_loader.rb $(DUMP_AST_TARGET)

dump_ast$(BUILD_EXEEXT): $(tooldir)/dump_ast.c $(LIBPRISM_OBJS) revision.h
dump_ast$(BUILD_EXEEXT): $(tooldir)/dump_ast.c $(LIBPRISM_OBJS)
$(ECHO) compiling $@
$(Q) $(CC) $(CFLAGS) $(OUTFLAG)$@ $(INCFLAGS) $(tooldir)/dump_ast.c $(LIBPRISM_OBJS)

build-tool/Makefile: $(tooldir)/dump_ast.mkmf.rb prism-srcs prism-incs revision.h
build-tool/Makefile: $(tooldir)/dump_ast.mkmf.rb prism-srcs prism-incs
+$(BASERUBY) -s $(tooldir)/dump_ast.mkmf.rb \
"-INCFLAGS=$(INCFLAGS)" "-make=$(MAKE)" "-objext=$(OBJEXT)" \
build-tool $(tooldir)/dump_ast.c dump_ast.$(OBJEXT) $(LIBPRISM_OBJS)
Expand Down
7 changes: 7 additions & 0 deletions doc/jit/zjit.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,13 @@ Collect stats without printing (access via `RubyVM::ZJIT.stats` in Ruby):
./miniruby --zjit-stats=quiet script.rb
```

Dump stats to a file. Name the file with a `.json` extension to write the
stats as pretty-printed JSON instead of the human-readable text format:

```bash
ruby --zjit-stats=stats.json script.rb
```

### Accessing Stats in Ruby

```ruby
Expand Down
58 changes: 49 additions & 9 deletions gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,13 @@ typedef struct mark_stack {

typedef int (*gc_compact_compare_func)(const void *l, const void *r, void *d);

typedef struct rb_heap_newobj {
uintptr_t alloc_cursor;
uintptr_t alloc_cursor_end;
struct free_region *alloc_next_region;
struct heap_page *alloc_using_page;
} rb_heap_newobj_t;

typedef struct rb_heap_struct {
short slot_size;

Expand All @@ -518,12 +525,7 @@ typedef struct rb_heap_struct {
size_t empty_slots;

/* Bump-pointer allocation state; only this objspace's owner thread writes it. */
struct {
uintptr_t alloc_cursor;
uintptr_t alloc_cursor_end;
struct free_region *alloc_next_region;
struct heap_page *alloc_using_page;
} newobj;
rb_heap_newobj_t newobj;

struct heap_page *free_pages;
struct ccan_list_head pages;
Expand Down Expand Up @@ -2981,10 +2983,40 @@ bool
rb_gc_impl_zjit_new_obj_fastpath(void *objspace_ptr, size_t alloc_size, VALUE flags, VALUE klass,
struct rb_gc_zjit_fastpath *fastpath)
{
/* Bump-pointer allocation state lives in the per-objspace heaps, but ZJIT's inline
* fastpath assumes a separate cache structure; report "no fastpath" (as gc/wbcheck
* does). The heaps are single-writer, so one could be offered later. */
#if USE_ZJIT
size_t heap_idx = 0;
size_t slot_size = 0;
for (; heap_idx < HEAP_COUNT; heap_idx++) {
if (alloc_size + RVALUE_OVERHEAD <= pool_slot_sizes[heap_idx]) {
slot_size = pool_slot_sizes[heap_idx];
break;
}
}
if (slot_size == 0) return false;

#undef heaps
size_t base = offsetof(rb_objspace_t, heaps)
+ heap_idx * sizeof(rb_heap_t)
+ offsetof(rb_heap_t, newobj);
#define heaps objspace->heaps

struct rb_gc_zjit_default_new_obj_fastpath default_fastpath = {
base + offsetof(rb_heap_newobj_t, alloc_cursor),
base + offsetof(rb_heap_newobj_t, alloc_cursor_end),
slot_size,
base - offsetof(rb_heap_t, newobj) + offsetof(rb_heap_t, total_allocated_objects),
flags,
klass
};

memset(fastpath, 0, sizeof(*fastpath));
fastpath->kind = RB_GC_ZJIT_FASTPATH_DEFAULT;
memcpy(fastpath->data.words, &default_fastpath, sizeof(default_fastpath));

return true;
#else
return false;
#endif
}

NOINLINE(static VALUE newobj_refill(rb_objspace_t *objspace, size_t heap_idx));
Expand Down Expand Up @@ -3071,6 +3103,11 @@ newobj_slowpath(VALUE klass, VALUE flags, rb_objspace_t *objspace, int wb_protec
obj = newobj_alloc(objspace, heap_idx);
newobj_init(klass, flags, wb_protected, objspace, obj);

if (RB_UNLIKELY(ruby_gc_stressful)) {
rb_heap_t *heap = &heaps[heap_idx];
heap->newobj.alloc_cursor_end = heap->newobj.alloc_cursor;
}

return obj;
}

Expand Down Expand Up @@ -5387,6 +5424,9 @@ rgengc_check_relation(rb_objspace_t *objspace, VALUE obj)
if (objspace->rgengc.parent_object_old_p) {
if (RVALUE_WB_UNPROTECTED(objspace, obj) || !RVALUE_OLD_P(objspace, obj)) {
rgengc_remember(objspace, objspace->rgengc.parent_object);
/* It is in the rememberset now, so its remaining children have nothing left
* to ask for: stop testing them. */
objspace->rgengc.parent_object_old_p = false;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions gc/default/zjit_fastpath.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct rb_gc_zjit_default_new_obj_fastpath {
size_t cursor_offset;
size_t cursor_end_offset;
size_t slot_size;
size_t total_allocated_objects_offset;
VALUE flags;
VALUE klass;
};
Expand Down
2 changes: 1 addition & 1 deletion gc/gc.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ MODULAR_GC_FN void rb_gc_rp(VALUE);
MODULAR_GC_FN void rb_gc_handle_weak_references(VALUE obj);
MODULAR_GC_FN bool rb_gc_obj_needs_cleanup_p(VALUE obj);

void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context);
#if USE_MODULAR_GC
MODULAR_GC_FN bool rb_gc_event_hook_required_p(rb_event_flag_t event);
MODULAR_GC_FN void *rb_gc_get_ractor_newobj_cache(void);
MODULAR_GC_FN void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context);
MODULAR_GC_FN void rb_gc_move_obj_during_marking(VALUE from, VALUE to);
MODULAR_GC_FN void rb_gc_print_backtrace();
#endif
Expand Down
9 changes: 6 additions & 3 deletions include/ruby/st.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,16 @@ struct st_table_entry; /* defined in st.c */

struct st_table {
/* Cached features of the table -- see st.c for more details. */
unsigned char entry_power, bin_power, size_ind, entries_start;
unsigned char entry_power, bin_power, size_ind;
/* How many times the table was rebuilt. */
unsigned int rebuilds_num;
unsigned char rebuilds_num;
/* Start index of entries in array entries. */
unsigned int entries_start;

const struct st_hash_type *type;
/* Number of entries currently in the table. */
st_index_t num_entries;
/* Start and bound index of entries in array entries.
/* bound index of entries in array entries.
entries_starts and entries_bound are in interval
[0,allocated_entries]. */
st_index_t entries_bound;
Expand Down
9 changes: 6 additions & 3 deletions internal/set_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ typedef struct set_table_entry set_table_entry;

struct set_table {
/* Cached features of the table -- see st.c for more details. */
unsigned char entry_power, bin_power, size_ind, entries_start;
unsigned char entry_power, bin_power, size_ind;
/* How many times the table was rebuilt. */
unsigned int rebuilds_num;
unsigned char rebuilds_num;

/* Start index of entries in array entries. */
unsigned int entries_start;
const struct st_hash_type *type;
/* Number of entries currently in the table. */
st_index_t num_entries;

/* Start and bound index of entries in array entries.
/* bound index of entries in array entries.
entries_starts and entries_bound are in interval
[0,allocated_entries]. */
st_index_t entries_bound;
Expand Down
12 changes: 3 additions & 9 deletions st.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
#define ATTRIBUTE_UNUSED
#endif

#define MAX_ENTRIES_START ((unsigned char)-1)
#define MAX_ENTRIES_START ((unsigned int)-1)

/* The type of hashes. */
typedef st_index_t st_hash_t;
Expand Down Expand Up @@ -1387,10 +1387,7 @@ update_range_for_deleted(st_table *tab, st_index_t n)
st_index_t bound = tab->entries_bound;
st_table_entry *entries = tab->entries;
while (start < bound && DELETED_ENTRY_P(&entries[start])) start++;
if (start > MAX_ENTRIES_START) {
start = MAX_ENTRIES_START;
}
tab->entries_start = start;
tab->entries_start = start > MAX_ENTRIES_START ? MAX_ENTRIES_START : (unsigned int)start;
}
}

Expand Down Expand Up @@ -3086,10 +3083,7 @@ set_update_range_for_deleted(set_table *tab, st_index_t n)
st_index_t bound = tab->entries_bound;
set_table_entry *entries = tab->entries;
while (start < bound && DELETED_ENTRY_P(&entries[start])) start++;
if (start > MAX_ENTRIES_START) {
start = MAX_ENTRIES_START;
}
tab->entries_start = start;
tab->entries_start = start > MAX_ENTRIES_START ? MAX_ENTRIES_START : (unsigned int)start;
}
}

Expand Down
21 changes: 17 additions & 4 deletions string.c
Original file line number Diff line number Diff line change
Expand Up @@ -3174,6 +3174,11 @@ rb_str_sublen(VALUE str, long pos)
}
}

/* Substrings that need a slot larger than this are shared instead of copied.
* Larger slots hold fewer objects per page and trigger GC more often, which
* outweighs the copy they save; see [Feature #22186] for the benchmarks. */
#define STR_SUBSEQ_MAX_EMBED_SIZE 256

static VALUE
str_subseq(VALUE str, long beg, long len)
{
Expand All @@ -3193,12 +3198,19 @@ str_subseq(VALUE str, long beg, long len)
return str2;
}

str2 = str_alloc_heap(rb_cString);
if (str_embed_capa(str2) >= len + termlen) {
/* Sharing allocates a shared root as well unless str can be one itself, so
* a copy is worth a larger slot only when it saves that second object. */
const bool root_available = STR_SHARED_P(str) ||
RB_FL_TEST_RAW(str, FL_FREEZE | STR_CHILLED) == FL_FREEZE;
const size_t max_embed_size = root_available ?
rb_gc_size_slot_size(sizeof(struct RString)) : STR_SUBSEQ_MAX_EMBED_SIZE;
const size_t embed_size = rb_str_embed_size(len, termlen);

if (embed_size <= max_embed_size && rb_gc_size_allocatable_p(embed_size)) {
str2 = str_alloc_embed(rb_cString, len + termlen);
char *ptr2 = RSTRING(str2)->as.embed.ary;
STR_SET_EMBED(str2);
memcpy(ptr2, RSTRING_PTR(str) + beg, len);
TERM_FILL(ptr2+len, termlen);
TERM_FILL(ptr2 + len, termlen);

STR_SET_LEN(str2, len);
if (ENC_CODERANGE(str) == ENC_CODERANGE_7BIT) {
Expand All @@ -3208,6 +3220,7 @@ str_subseq(VALUE str, long beg, long len)
RB_GC_GUARD(str);
}
else {
str2 = str_alloc_heap(rb_cString);
str_replace_shared(str2, str);
RUBY_ASSERT(!STR_EMBED_P(str2));
if (ENC_CODERANGE(str) != ENC_CODERANGE_7BIT) {
Expand Down
2 changes: 1 addition & 1 deletion test/-ext-/string/test_rb_str_dup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
require '-test-/string'

class Test_RbStrDup < Test::Unit::TestCase
STR_DUPLICATE_MAX_EMBED_LEN = 999 # From macro defined in string.c
STR_DUPLICATE_MAX_EMBED_LEN = 256 - (RbConfig::SIZEOF["void*"] * 3) - 1 # From macro defined in string.c

def test_nested_shared_non_frozen
orig_str = "a" * (STR_DUPLICATE_MAX_EMBED_LEN + 1)
Expand Down
21 changes: 21 additions & 0 deletions test/ruby/test_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3639,6 +3639,27 @@ def test_shared_middle_string_terminator
refute_includes ObjectSpace.dump(substr), ' "shared":true,'
end

def test_substring_embed
str = "a" * 448

require 'objspace'

# 128 and 320 sit either side of STR_SUBSEQ_MAX_EMBED_SIZE in string.c, which
# the copy has to fit in along with the header and the terminator
substr = str.byteslice(320, 128)
assert_equal "a" * 128, substr
assert_includes ObjectSpace.dump(substr), ' "embedded":true,'

substr = str.byteslice(128, 320)
assert_equal "a" * 320, substr
assert_includes ObjectSpace.dump(substr), ' "shared":true,'

# A frozen source is a shared root itself, so the same substring is shared
substr = str.freeze.byteslice(320, 128)
assert_equal "a" * 128, substr
assert_includes ObjectSpace.dump(substr), ' "shared":true,'
end

def test_unknown_string_option
str = nil
assert_nothing_raised(SyntaxError) do
Expand Down
18 changes: 18 additions & 0 deletions test/ruby/test_zjit_cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ def test = 42
assert_equal("true\n", out)
assert_equal stats_header, File.open(stats_file) {|f| f.gets(chomp: true)}, "should be overwritten"
}

# With --zjit-stats=<path> ending in .json, stats should be dumped as JSON
Tempfile.create(["zjit-stats-", ".json"]) {|tmp|
stats_file = tmp.path
tmp.puts("Lorem ipsum dolor sit amet, consectetur adipiscing elit, ...")
tmp.close

out, err, status = eval_with_jit(script, stats: stats_file)
assert_success(out, err, status)
refute_includes(err, stats_header)
assert_equal("true\n", out)

require "json"
json = JSON.parse(File.read(stats_file))
assert_kind_of Hash, json, "should be JSON"
assert json.key?("compiled_iseq_count"), "should contain stats keys"
refute_includes File.read(stats_file), stats_header, "should not contain the text stats header"
}
end

def test_enable_through_env
Expand Down
32 changes: 2 additions & 30 deletions tool/dump_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "revision.h"

/*
* When prism is compiled as part of CRuby, the xmalloc/xfree/etc. macros are
Expand All @@ -28,25 +27,12 @@ print_error(const pm_diagnostic_t *diagnostic, void *data)
fprintf(stderr, "%" PRIi32 ":%" PRIu32 ":%s\n", line_column.line, line_column.column, pm_diagnostic_message(diagnostic));
}

#if defined(RUBY_RELEASE_DATETIME) && defined(RUBY_RELEASE_DATETIME)
# define SHOW_PROGRAM_VERSION 2
#elif defined(RUBY_RELEASE_DATETIME) || defined(RUBY_RELEASE_DATETIME)
# define SHOW_PROGRAM_VERSION 1
#else
# define SHOW_PROGRAM_VERSION 0
#endif
#if SHOW_PROGRAM_VERSION
# define usage_versions "and program versions"
#else
# define usage_versions "version"
#endif

static void
usage(const char *prog)
{
fprintf(stderr, "Usage: %s [options]... <filename>\n"
"Options:\n"
" -v, --version: show Prism " usage_versions "\n"
" -v, --version: show Prism version\n"
" -h, --help: show this message\n"
"", prog);
}
Expand All @@ -69,21 +55,7 @@ main(int argc, const char *argv[])
if (!arg[2]) break;
if (strcmp(arg + 2, "version") == 0) {
version:
fputs("Prism " PRISM_VERSION
#if SHOW_PROGRAM_VERSION
" ["
# ifdef RUBY_RELEASE_DATETIME
RUBY_RELEASE_DATETIME
# endif
# if SHOW_PROGRAM_VERSION > 1
" "
# endif
# ifdef RUBY_REVISION
RUBY_REVISION
# endif
"]"
#endif
"\n", stdout);
fputs("Prism " PRISM_VERSION "\n", stdout);
return EXIT_SUCCESS;
}
if (strcmp(arg + 2, "help") == 0) {
Expand Down
4 changes: 4 additions & 0 deletions vm_insnhelper.c
Original file line number Diff line number Diff line change
Expand Up @@ -6141,6 +6141,10 @@ enum method_explorer_type {
mexp_search_super,
};

ALWAYS_INLINE(static VALUE vm_sendish(struct rb_execution_context_struct *ec,
struct rb_control_frame_struct *reg_cfp,
struct rb_call_data *cd, VALUE block_handler,
enum method_explorer_type method_explorer));
static inline VALUE
vm_sendish(
struct rb_execution_context_struct *ec,
Expand Down
Loading