Merge branch 'master' into wbcheck_random

This commit is contained in:
John Hawthorn 2025-08-13 11:12:48 -07:00 committed by GitHub
commit ab71d88e66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 562 additions and 256 deletions

2
depend
View file

@ -14598,6 +14598,7 @@ ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_newline_list.h
ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_string.h
ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h
ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h
ruby.$(OBJEXT): $(top_srcdir)/version.h
ruby.$(OBJEXT): {$(VPATH)}assert.h
ruby.$(OBJEXT): {$(VPATH)}atomic.h
ruby.$(OBJEXT): {$(VPATH)}backward/2/assume.h
@ -14781,6 +14782,7 @@ ruby.$(OBJEXT): {$(VPATH)}prism/ast.h
ruby.$(OBJEXT): {$(VPATH)}prism/diagnostic.h
ruby.$(OBJEXT): {$(VPATH)}prism/version.h
ruby.$(OBJEXT): {$(VPATH)}prism_compile.h
ruby.$(OBJEXT): {$(VPATH)}revision.h
ruby.$(OBJEXT): {$(VPATH)}ruby.c
ruby.$(OBJEXT): {$(VPATH)}ruby_assert.h
ruby.$(OBJEXT): {$(VPATH)}ruby_atomic.h

View file

@ -367,11 +367,11 @@ Outstanding ones only.
* Fiber.blocking? tells whether the current execution context is
blocking. [[Feature #16786]]
* Thread
* Thread#join invokes the scheduler hooks `block`/`unblock` in a
non-blocking execution context. [[Feature #16786]]
* Thread
* Thread.ignore_deadlock accessor has been added for disabling the
default deadlock detection, allowing the use of signal handlers to
break deadlock. [[Bug #13768]]

View file

@ -1,3 +1,10 @@
<!---
CAUTION
This page on docs.ruby-lang.org is displayed in Ruby's help message (-h and --help).
Please make sure you update the link when renaming or moving this file.
--->
# Ruby Command-Line Options
## About the Examples

View file

@ -29,6 +29,7 @@
#include "ruby/util.h"
#include "ruby_assert.h"
#include "vm_sync.h"
#include "ruby_atomic.h"
#ifndef ENC_DEBUG
#define ENC_DEBUG 0
@ -144,10 +145,14 @@ enc_list_update(int index, rb_raw_encoding *encoding)
{
RUBY_ASSERT(index < ENCODING_LIST_CAPA);
VALUE list = rb_encoding_list;
VALUE list = RUBY_ATOMIC_VALUE_LOAD(rb_encoding_list);
if (list && NIL_P(rb_ary_entry(list, index))) {
VALUE new_list = rb_ary_dup(list);
RBASIC_CLEAR_CLASS(new_list);
/* initialize encoding data */
rb_ary_store(list, index, enc_new(encoding));
rb_ary_store(new_list, index, enc_new(encoding));
RUBY_ATOMIC_VALUE_SET(rb_encoding_list, new_list);
}
}
@ -157,7 +162,7 @@ enc_list_lookup(int idx)
VALUE list, enc = Qnil;
if (idx < ENCODING_LIST_CAPA) {
list = rb_encoding_list;
list = RUBY_ATOMIC_VALUE_LOAD(rb_encoding_list);
RUBY_ASSERT(list);
enc = rb_ary_entry(list, idx);
}
@ -258,6 +263,7 @@ must_encindex(int index)
int
rb_to_encoding_index(VALUE enc)
{
ASSERT_vm_unlocking(); // can load encoding, so must not hold VM lock
int idx;
const char *name;
@ -667,15 +673,15 @@ int
rb_enc_alias(const char *alias, const char *orig)
{
int idx, r;
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
enc_check_addable(enc_table, alias); // can raise
}
idx = rb_enc_find_index(orig);
if (idx < 0) return -1;
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
enc_check_addable(enc_table, alias);
if ((idx = rb_enc_find_index(orig)) < 0) {
r = -1;
}
else {
r = enc_alias(enc_table, alias, idx);
}
r = enc_alias(enc_table, alias, idx);
}
return r;
@ -742,6 +748,7 @@ int rb_require_internal_silent(VALUE fname);
static int
load_encoding(const char *name)
{
ASSERT_vm_unlocking();
VALUE enclib = rb_sprintf("enc/%s.so", name);
VALUE debug = ruby_debug;
VALUE errinfo;
@ -757,7 +764,7 @@ load_encoding(const char *name)
enclib = rb_fstring(enclib);
ruby_debug = Qfalse;
errinfo = rb_errinfo();
loaded = rb_require_internal_silent(enclib);
loaded = rb_require_internal_silent(enclib); // must run without VM_LOCK
ruby_debug = debug;
rb_set_errinfo(errinfo);
@ -781,6 +788,7 @@ enc_autoload_body(rb_encoding *enc)
{
rb_encoding *base;
int i = 0;
ASSERT_vm_unlocking();
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
base = enc_table->list[ENC_TO_ENCINDEX(enc)].base;
@ -792,30 +800,32 @@ enc_autoload_body(rb_encoding *enc)
}
} while (enc_table->list[i].enc != base && (++i, 1));
}
}
if (i != -1) {
if (base) {
bool do_register = true;
if (rb_enc_autoload_p(base)) {
if (rb_enc_autoload(base) < 0) {
do_register = false;
i = -1;
}
if (i != -1) {
if (base) {
bool do_register = true;
if (rb_enc_autoload_p(base)) {
if (rb_enc_autoload(base) < 0) {
do_register = false;
i = -1;
}
}
i = enc->ruby_encoding_index;
if (do_register) {
if (do_register) {
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
i = enc->ruby_encoding_index;
enc_register_at(enc_table, i & ENC_INDEX_MASK, rb_enc_name(enc), base);
((rb_raw_encoding *)enc)->ruby_encoding_index = i;
}
}
i &= ENC_INDEX_MASK;
}
else {
i = -2;
}
i &= ENC_INDEX_MASK;
}
else {
i = -2;
}
}
return i;
@ -824,6 +834,7 @@ enc_autoload_body(rb_encoding *enc)
int
rb_enc_autoload(rb_encoding *enc)
{
ASSERT_vm_unlocking();
int i = enc_autoload_body(enc);
if (i == -2) {
i = load_encoding(rb_enc_name(enc));
@ -844,6 +855,7 @@ int
rb_enc_find_index(const char *name)
{
int i;
ASSERT_vm_unlocking(); // it needs to be unlocked so it can call `load_encoding` if necessary
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
i = enc_registered(enc_table, name);
}
@ -1019,7 +1031,6 @@ rb_enc_associate_index(VALUE obj, int idx)
rb_encoding *enc;
int oldidx, oldtermlen, termlen;
/* enc_check_capable(obj);*/
rb_check_frozen(obj);
oldidx = rb_enc_get_index(obj);
if (oldidx == idx)
@ -1355,7 +1366,10 @@ enc_names(VALUE self)
args[0] = (VALUE)rb_to_encoding_index(self);
args[1] = rb_ary_new2(0);
st_foreach(global_enc_table.names, enc_names_i, (st_data_t)args);
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
st_foreach(enc_table->names, enc_names_i, (st_data_t)args);
}
return args[1];
}
@ -1380,8 +1394,9 @@ enc_names(VALUE self)
static VALUE
enc_list(VALUE klass)
{
VALUE ary = rb_ary_new2(0);
rb_ary_replace(ary, rb_encoding_list);
VALUE ary = rb_ary_new2(ENCODING_LIST_CAPA);
VALUE list = RUBY_ATOMIC_VALUE_LOAD(rb_encoding_list);
rb_ary_replace(ary, list);
return ary;
}
@ -1526,6 +1541,9 @@ int rb_locale_charmap_index(void);
int
rb_locale_encindex(void)
{
// `rb_locale_charmap_index` can call `enc_find_index`, which can
// load an encoding. This needs to be done without VM lock held.
ASSERT_vm_unlocking();
int idx = rb_locale_charmap_index();
if (idx < 0) idx = ENCINDEX_UTF_8;
@ -1584,6 +1602,10 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha
/* Already set */
overridden = TRUE;
if (!NIL_P(encoding)) {
enc_check_encoding(encoding); // loads it if necessary. Needs to be done outside of VM lock.
}
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
if (NIL_P(encoding)) {
def->index = -1;
@ -1854,8 +1876,11 @@ rb_enc_name_list_i(st_data_t name, st_data_t idx, st_data_t arg)
static VALUE
rb_enc_name_list(VALUE klass)
{
VALUE ary = rb_ary_new2(global_enc_table.names->num_entries);
st_foreach(global_enc_table.names, rb_enc_name_list_i, (st_data_t)ary);
VALUE ary;
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
ary = rb_ary_new2(enc_table->names->num_entries);
st_foreach(enc_table->names, rb_enc_name_list_i, (st_data_t)ary);
}
return ary;
}
@ -1901,7 +1926,9 @@ rb_enc_aliases(VALUE klass)
aliases[0] = rb_hash_new();
aliases[1] = rb_ary_new();
st_foreach(global_enc_table.names, rb_enc_aliases_enc_i, (st_data_t)aliases);
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
st_foreach(enc_table->names, rb_enc_aliases_enc_i, (st_data_t)aliases);
}
return aliases[0];
}
@ -1969,9 +1996,9 @@ Init_Encoding(void)
struct enc_table *enc_table = &global_enc_table;
rb_gc_register_address(&rb_encoding_list);
list = rb_encoding_list = rb_ary_new2(ENCODING_LIST_CAPA);
RBASIC_CLEAR_CLASS(list);
rb_vm_register_global_object(list);
for (i = 0; i < enc_table->count; ++i) {
rb_ary_push(list, enc_new(enc_table->list[i].enc));
@ -2003,5 +2030,7 @@ Init_encodings(void)
void
rb_enc_foreach_name(int (*func)(st_data_t name, st_data_t idx, st_data_t arg), st_data_t arg)
{
st_foreach(global_enc_table.names, func, arg);
GLOBAL_ENC_TABLE_LOCKING(enc_table) {
st_foreach(enc_table->names, func, arg);
}
}

43
gc.c
View file

@ -1047,7 +1047,7 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU
{
RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1);
if (klass) rb_data_object_check(klass);
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)datap, (VALUE)dfree, !dmark, sizeof(struct RTypedData));
return newobj_of(GET_RACTOR(), klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, !dmark, sizeof(struct RTypedData));
}
VALUE
@ -1064,7 +1064,7 @@ typed_data_alloc(VALUE klass, VALUE typed_flag, void *datap, const rb_data_type_
RBIMPL_NONNULL_ARG(type);
if (klass) rb_data_object_check(klass);
bool wb_protected = (type->flags & RUBY_FL_WB_PROTECTED) || !type->function.dmark;
return newobj_of(GET_RACTOR(), klass, T_DATA, ((VALUE)type) | IS_TYPED_DATA | typed_flag, (VALUE)datap, 0, wb_protected, size);
return newobj_of(GET_RACTOR(), klass, T_DATA, 0, ((VALUE)type) | IS_TYPED_DATA | typed_flag, (VALUE)datap, wb_protected, size);
}
VALUE
@ -1921,7 +1921,7 @@ object_id(VALUE obj)
// in fields.
return class_object_id(obj);
case T_IMEMO:
rb_bug("T_IMEMO can't have an object_id");
RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields));
break;
default:
break;
@ -1945,20 +1945,26 @@ build_id2ref_i(VALUE obj, void *data)
switch (BUILTIN_TYPE(obj)) {
case T_CLASS:
case T_MODULE:
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
if (RCLASS(obj)->object_id) {
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
st_insert(id2ref_tbl, RCLASS(obj)->object_id, obj);
}
break;
case T_IMEMO:
case T_NONE:
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
if (IMEMO_TYPE_P(obj, imemo_fields) && rb_shape_obj_has_id(obj)) {
st_insert(id2ref_tbl, rb_obj_id(obj), rb_imemo_fields_owner(obj));
}
break;
default:
case T_OBJECT:
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
if (rb_shape_obj_has_id(obj)) {
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
st_insert(id2ref_tbl, rb_obj_id(obj), obj);
}
break;
default:
// For generic_fields, the T_IMEMO/fields is responsible for populating the entry.
break;
}
}
@ -3173,10 +3179,15 @@ rb_gc_mark_children(void *objspace, VALUE obj)
break;
case T_DATA: {
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
bool typed_data = RTYPEDDATA_P(obj);
void *const ptr = typed_data ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
if (typed_data) {
gc_mark_internal(RTYPEDDATA(obj)->fields_obj);
}
if (ptr) {
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
if (typed_data && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj);
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
@ -3184,7 +3195,7 @@ rb_gc_mark_children(void *objspace, VALUE obj)
}
}
else {
RUBY_DATA_FUNC mark_func = RTYPEDDATA_P(obj) ?
RUBY_DATA_FUNC mark_func = typed_data ?
RTYPEDDATA_TYPE(obj)->function.dmark :
RDATA(obj)->dmark;
if (mark_func) (*mark_func)(ptr);
@ -4121,9 +4132,15 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
case T_DATA:
/* Call the compaction callback, if it exists */
{
void *const ptr = RTYPEDDATA_P(obj) ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
bool typed_data = RTYPEDDATA_P(obj);
void *const ptr = typed_data ? RTYPEDDATA_GET_DATA(obj) : DATA_PTR(obj);
if (typed_data) {
UPDATE_IF_MOVED(objspace, RTYPEDDATA(obj)->fields_obj);
}
if (ptr) {
if (RTYPEDDATA_P(obj) && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
if (typed_data && gc_declarative_marking_p(RTYPEDDATA_TYPE(obj))) {
size_t *offset_list = TYPED_DATA_REFS_OFFSET_LIST(obj);
for (size_t offset = *offset_list; offset != RUBY_REF_END; offset = *offset_list++) {
@ -4131,7 +4148,7 @@ rb_gc_update_object_references(void *objspace, VALUE obj)
*ref = gc_location_internal(objspace, *ref);
}
}
else if (RTYPEDDATA_P(obj)) {
else if (typed_data) {
RUBY_DATA_FUNC compact_func = RTYPEDDATA_TYPE(obj)->function.dcompact;
if (compact_func) (*compact_func)(ptr);
}

79
gc.rb
View file

@ -50,14 +50,14 @@ module GC
end
# call-seq:
# GC.enable -> true or false
# GC.enable -> true or false
#
# Enables garbage collection, returning +true+ if garbage
# collection was previously disabled.
# Enables garbage collection;
# returns whether garbage collection was disabled:
#
# GC.disable #=> false
# GC.enable #=> true
# GC.enable #=> false
# GC.disable
# GC.enable # => true
# GC.enable # => false
#
def self.enable
Primitive.gc_enable
@ -66,11 +66,13 @@ module GC
# call-seq:
# GC.disable -> true or false
#
# Disables garbage collection, returning +true+ if garbage
# collection was already disabled.
# Disables garbage collection (but GC.start remains potent):
# returns whether garbage collection was already disabled.
#
# GC.enable
# GC.disable # => false
# GC.disable # => true
#
# GC.disable #=> false
# GC.disable #=> true
def self.disable
Primitive.gc_disable
end
@ -102,9 +104,14 @@ module GC
end
# call-seq:
# GC.count -> Integer
# self.count -> integer
#
# Returns the total number of times garbage collection has occurred:
#
# GC.count # => 385
# GC.start
# GC.count # => 386
#
# Returns the number of times \GC has occurred since the process started.
def self.count
Primitive.gc_count
end
@ -322,19 +329,47 @@ module GC
end
# call-seq:
# GC.latest_gc_info -> hash
# GC.latest_gc_info(hash) -> hash
# GC.latest_gc_info(key) -> value
# GC.latest_gc_info -> new_hash
# GC.latest_gc_info(key) -> value
# GC.latest_gc_info(hash) -> hash
#
# Returns information about the most recent garbage collection.
# With no argument given,
# returns information about the most recent garbage collection:
#
# If the argument +hash+ is given and is a Hash object,
# it is overwritten and returned.
# This is intended to avoid the probe effect.
# GC.latest_gc_info
# # =>
# {major_by: :force,
# need_major_by: nil,
# gc_by: :method,
# have_finalizer: false,
# immediate_sweep: true,
# state: :none,
# weak_references_count: 0,
# retained_weak_references_count: 0}
#
# With symbol argument +key+ given,
# returns the value for that key:
#
# GC.latest_gc_info(:gc_by) # => :newobj
#
# With hash argument +hash+ given,
# returns that hash with GC information merged into its content;
# this form may be useful in minimizing {probe effects}[https://en.wikipedia.org/wiki/Probe_effect]:
#
# h = {foo: 0, bar: 1}
# GC.latest_gc_info(h)
# # =>
# {foo: 0,
# bar: 1,
# major_by: nil,
# need_major_by: nil,
# gc_by: :newobj,
# have_finalizer: false,
# immediate_sweep: false,
# state: :sweeping,
# weak_references_count: 0,
# retained_weak_references_count: 0}
#
# If the argument +key+ is given and is a Symbol object,
# it returns the value associated with the key.
# This is equivalent to <tt>GC.latest_gc_info[key]</tt>.
def self.latest_gc_info hash_or_key = nil
if hash_or_key == nil
hash_or_key = {}

37
hash.c
View file

@ -5192,25 +5192,26 @@ env_enc_str_new(const char *ptr, long len, rb_encoding *enc)
}
static VALUE
env_str_new(const char *ptr, long len)
env_str_new(const char *ptr, long len, rb_encoding *enc)
{
return env_enc_str_new(ptr, len, env_encoding());
return env_enc_str_new(ptr, len, enc);
}
static VALUE
env_str_new2(const char *ptr)
env_str_new2(const char *ptr, rb_encoding *enc)
{
if (!ptr) return Qnil;
return env_str_new(ptr, strlen(ptr));
return env_str_new(ptr, strlen(ptr), enc);
}
static VALUE
getenv_with_lock(const char *name)
{
VALUE ret;
rb_encoding *enc = env_encoding();
ENV_LOCKING() {
const char *val = getenv(name);
ret = env_str_new2(val);
ret = env_str_new2(val, enc);
}
return ret;
}
@ -5773,13 +5774,14 @@ env_values(void)
{
VALUE ary = rb_ary_new();
rb_encoding *enc = env_encoding();
ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
rb_ary_push(ary, env_str_new2(s+1));
rb_ary_push(ary, env_str_new2(s+1, enc));
}
env++;
}
@ -5865,14 +5867,15 @@ env_each_pair(VALUE ehash)
VALUE ary = rb_ary_new();
rb_encoding *enc = env_encoding();
ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
rb_ary_push(ary, env_str_new(*env, s-*env));
rb_ary_push(ary, env_str_new2(s+1));
rb_ary_push(ary, env_str_new(*env, s-*env, enc));
rb_ary_push(ary, env_str_new2(s+1, enc));
}
env++;
}
@ -6255,13 +6258,14 @@ env_to_a(VALUE _)
{
VALUE ary = rb_ary_new();
rb_encoding *enc = env_encoding();
ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
rb_ary_push(ary, rb_assoc_new(env_str_new(*env, s-*env),
env_str_new2(s+1)));
rb_ary_push(ary, rb_assoc_new(env_str_new(*env, s-*env, enc),
env_str_new2(s+1, enc)));
}
env++;
}
@ -6509,6 +6513,7 @@ env_key(VALUE dmy, VALUE value)
StringValue(value);
VALUE str = Qnil;
rb_encoding *enc = env_encoding();
ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
@ -6516,7 +6521,7 @@ env_key(VALUE dmy, VALUE value)
if (s++) {
long len = strlen(s);
if (RSTRING_LEN(value) == len && strncmp(s, RSTRING_PTR(value), len) == 0) {
str = env_str_new(*env, s-*env-1);
str = env_str_new(*env, s-*env-1, enc);
break;
}
}
@ -6533,13 +6538,14 @@ env_to_hash(void)
{
VALUE hash = rb_hash_new();
rb_encoding *enc = env_encoding();
ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
while (*env) {
char *s = strchr(*env, '=');
if (s) {
rb_hash_aset(hash, env_str_new(*env, s-*env),
env_str_new2(s+1));
rb_hash_aset(hash, env_str_new(*env, s-*env, enc),
env_str_new2(s+1, enc));
}
env++;
}
@ -6684,14 +6690,15 @@ env_shift(VALUE _)
VALUE result = Qnil;
VALUE key = Qnil;
rb_encoding *enc = env_encoding();
ENV_LOCKING() {
char **env = GET_ENVIRON(environ);
if (*env) {
const char *p = *env;
char *s = strchr(p, '=');
if (s) {
key = env_str_new(p, s-p);
VALUE val = env_str_new2(getenv(RSTRING_PTR(key)));
key = env_str_new(p, s-p, enc);
VALUE val = env_str_new2(getenv(RSTRING_PTR(key)), enc);
result = rb_assoc_new(key, val);
}
}

26
imemo.c
View file

@ -109,16 +109,16 @@ rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt)
}
static VALUE
imemo_fields_new(VALUE klass, size_t capa)
imemo_fields_new(VALUE owner, size_t capa)
{
size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE);
if (rb_gc_size_allocatable_p(embedded_size)) {
VALUE fields = rb_imemo_new(imemo_fields, klass, embedded_size);
VALUE fields = rb_imemo_new(imemo_fields, owner, embedded_size);
RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields));
return fields;
}
else {
VALUE fields = rb_imemo_new(imemo_fields, klass, sizeof(struct rb_fields));
VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields));
FL_SET_RAW(fields, OBJ_FIELD_EXTERNAL);
IMEMO_OBJ_FIELDS(fields)->as.external.ptr = ALLOC_N(VALUE, capa);
return fields;
@ -126,23 +126,23 @@ imemo_fields_new(VALUE klass, size_t capa)
}
VALUE
rb_imemo_fields_new(VALUE klass, size_t capa)
rb_imemo_fields_new(VALUE owner, size_t capa)
{
return imemo_fields_new(klass, capa);
return imemo_fields_new(owner, capa);
}
static VALUE
imemo_fields_new_complex(VALUE klass, size_t capa)
imemo_fields_new_complex(VALUE owner, size_t capa)
{
VALUE fields = imemo_fields_new(klass, sizeof(struct rb_fields));
VALUE fields = imemo_fields_new(owner, sizeof(struct rb_fields));
IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa);
return fields;
}
VALUE
rb_imemo_fields_new_complex(VALUE klass, size_t capa)
rb_imemo_fields_new_complex(VALUE owner, size_t capa)
{
return imemo_fields_new_complex(klass, capa);
return imemo_fields_new_complex(owner, capa);
}
static int
@ -161,9 +161,9 @@ imemo_fields_complex_wb_i(st_data_t key, st_data_t value, st_data_t arg)
}
VALUE
rb_imemo_fields_new_complex_tbl(VALUE klass, st_table *tbl)
rb_imemo_fields_new_complex_tbl(VALUE owner, st_table *tbl)
{
VALUE fields = imemo_fields_new(klass, sizeof(struct rb_fields));
VALUE fields = imemo_fields_new(owner, sizeof(struct rb_fields));
IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl;
st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields);
return fields;
@ -176,7 +176,7 @@ rb_imemo_fields_clone(VALUE fields_obj)
VALUE clone;
if (rb_shape_too_complex_p(shape_id)) {
clone = rb_imemo_fields_new_complex(CLASS_OF(fields_obj), 0);
clone = rb_imemo_fields_new_complex(rb_imemo_fields_owner(fields_obj), 0);
RBASIC_SET_SHAPE_ID(clone, shape_id);
st_table *src_table = rb_imemo_fields_complex_tbl(fields_obj);
st_table *dest_table = rb_imemo_fields_complex_tbl(clone);
@ -184,7 +184,7 @@ rb_imemo_fields_clone(VALUE fields_obj)
st_foreach(dest_table, imemo_fields_complex_wb_i, (st_data_t)clone);
}
else {
clone = imemo_fields_new(CLASS_OF(fields_obj), RSHAPE_CAPACITY(shape_id));
clone = imemo_fields_new(rb_imemo_fields_owner(fields_obj), RSHAPE_CAPACITY(shape_id));
RBASIC_SET_SHAPE_ID(clone, shape_id);
VALUE *fields = rb_imemo_fields_ptr(clone);
attr_index_t fields_count = RSHAPE_LEN(shape_id);

View file

@ -24,7 +24,7 @@
* In released versions of Ruby, this number is not defined since teeny
* versions of Ruby should guarantee ABI compatibility.
*/
#define RUBY_ABI_VERSION 2
#define RUBY_ABI_VERSION 3
/* Windows does not support weak symbols so ruby_abi_version will not exist
* in the shared library. */

View file

@ -133,12 +133,6 @@ struct RData {
*/
RUBY_DATA_FUNC dmark;
/** Pointer to the actual C level struct that you want to wrap.
* This is in between dmark and dfree to allow DATA_PTR to continue
* to work for both RData and non-embedded RTypedData.
*/
void *data;
/**
* This function is called when the object is no longer used. You need to
* do whatever necessary to avoid memory leaks.
@ -147,6 +141,12 @@ struct RData {
* impossible at that moment (that is why GC runs).
*/
RUBY_DATA_FUNC dfree;
/** Pointer to the actual C level struct that you want to wrap.
* This is in between dmark and dfree to allow DATA_PTR to continue
* to work for both RData and non-embedded RTypedData.
*/
void *data;
};
RBIMPL_SYMBOL_EXPORT_BEGIN()

View file

@ -355,6 +355,9 @@ struct RTypedData {
/** The part that all ruby objects have in common. */
struct RBasic basic;
/** Direct reference to the slots that holds instance variables, if any **/
VALUE fields_obj;
/**
* This is a `const rb_data_type_t *const` value, with the low bits set:
*

View file

@ -546,7 +546,7 @@ RCLASS_WRITABLE_ENSURE_FIELDS_OBJ(VALUE obj)
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
rb_classext_t *ext = RCLASS_EXT_WRITABLE(obj);
if (!ext->fields_obj) {
RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_fields_new(rb_singleton_class(obj), 1));
RB_OBJ_WRITE(obj, &ext->fields_obj, rb_imemo_fields_new(obj, 1));
}
return ext->fields_obj;
}

View file

@ -273,12 +273,18 @@ struct rb_fields {
#define OBJ_FIELD_EXTERNAL IMEMO_FL_USER0
#define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields)
VALUE rb_imemo_fields_new(VALUE klass, size_t capa);
VALUE rb_imemo_fields_new_complex(VALUE klass, size_t capa);
VALUE rb_imemo_fields_new_complex_tbl(VALUE klass, st_table *tbl);
VALUE rb_imemo_fields_new(VALUE owner, size_t capa);
VALUE rb_imemo_fields_new_complex(VALUE owner, size_t capa);
VALUE rb_imemo_fields_new_complex_tbl(VALUE owner, st_table *tbl);
VALUE rb_imemo_fields_clone(VALUE fields_obj);
void rb_imemo_fields_clear(VALUE fields_obj);
static inline VALUE
rb_imemo_fields_owner(VALUE fields_obj)
{
return CLASS_OF(fields_obj);
}
static inline VALUE *
rb_imemo_fields_ptr(VALUE obj_fields)
{

View file

@ -15,13 +15,16 @@ struct set_table {
const struct st_hash_type *type;
/* Number of entries currently in the table. */
st_index_t num_entries;
/* Array of bins used for access by keys. */
st_index_t *bins;
/* Start and bound index of entries in array entries.
entries_starts and entries_bound are in interval
[0,allocated_entries]. */
st_index_t entries_start, entries_bound;
/* Array of size 2^entry_power. */
/**
* Array of size 2^entry_power.
* Followed by st_index_t *bins, Array of bins used for access by keys.
*/
set_table_entry *entries;
};

6
ruby.c
View file

@ -61,6 +61,7 @@
#include "ruby/util.h"
#include "ruby/version.h"
#include "ruby/internal/error.h"
#include "version.h"
#define singlebit_only_p(x) !((x) & ((x)-1))
STATIC_ASSERT(Qnil_1bit_from_Qfalse, singlebit_only_p(Qnil^Qfalse));
@ -403,7 +404,10 @@ usage(const char *name, int help, int highlight, int columns)
unsigned int w = (columns > 80 ? (columns - 79) / 2 : 0) + 16;
#define SHOW(m) show_usage_line(&(m), help, highlight, w, columns)
printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n", sb, se, name);
printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n\n", sb, se, name);
printf("Details and examples at https://docs.ruby-lang.org/en/%s/ruby/options_md.html\n",
RUBY_PATCHLEVEL == -1 ? "master" : STRINGIZE(RUBY_VERSION_MAJOR) "." STRINGIZE(RUBY_VERSION_MINOR));
for (i = 0; i < num; ++i)
SHOW(usage_msg[i]);

1
set.c
View file

@ -139,7 +139,6 @@ set_mark(void *ptr)
static void
set_free_embedded(struct set_object *sobj)
{
free((&sobj->table)->bins);
free((&sobj->table)->entries);
}

13
shape.c
View file

@ -877,8 +877,17 @@ shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings)
#endif
VALUE klass;
if (IMEMO_TYPE_P(obj, imemo_fields)) { // HACK
klass = CLASS_OF(obj);
if (IMEMO_TYPE_P(obj, imemo_fields)) {
VALUE owner = rb_imemo_fields_owner(obj);
switch (BUILTIN_TYPE(owner)) {
case T_CLASS:
case T_MODULE:
klass = rb_singleton_class(owner);
break;
default:
klass = rb_obj_class(owner);
break;
}
}
else {
klass = rb_obj_class(obj);

96
st.c
View file

@ -2395,18 +2395,44 @@ set_get_allocated_entries(const set_table *tab)
return ((st_index_t) 1)<<tab->entry_power;
}
static inline size_t
set_allocated_entries_size(const set_table *tab)
{
return set_get_allocated_entries(tab) * sizeof(set_table_entry);
}
static inline bool
set_has_bins(const set_table *tab)
{
return tab->entry_power > MAX_POWER2_FOR_TABLES_WITHOUT_BINS;
}
/* Return size of the allocated bins of table TAB. */
static inline st_index_t
set_bins_size(const set_table *tab)
{
return features[tab->entry_power].bins_words * sizeof (st_index_t);
if (set_has_bins(tab)) {
return features[tab->entry_power].bins_words * sizeof (st_index_t);
}
return 0;
}
static inline st_index_t *
set_bins_ptr(const set_table *tab)
{
if (set_has_bins(tab)) {
return (st_index_t *)(((char *)tab->entries) + set_allocated_entries_size(tab));
}
return NULL;
}
/* Mark all bins of table TAB as empty. */
static void
set_initialize_bins(set_table *tab)
{
memset(tab->bins, 0, set_bins_size(tab));
memset(set_bins_ptr(tab), 0, set_bins_size(tab));
}
/* Make table TAB empty. */
@ -2415,7 +2441,7 @@ set_make_tab_empty(set_table *tab)
{
tab->num_entries = 0;
tab->entries_start = tab->entries_bound = 0;
if (tab->bins != NULL)
if (set_bins_ptr(tab) != NULL)
set_initialize_bins(tab);
}
@ -2443,13 +2469,13 @@ set_init_existing_table_with_size(set_table *tab, const struct st_hash_type *typ
tab->entry_power = n;
tab->bin_power = features[n].bin_power;
tab->size_ind = features[n].size_ind;
if (n <= MAX_POWER2_FOR_TABLES_WITHOUT_BINS)
tab->bins = NULL;
else {
tab->bins = (st_index_t *) malloc(set_bins_size(tab));
size_t memsize = 0;
if (set_has_bins(tab)) {
memsize += set_bins_size(tab);
}
tab->entries = (set_table_entry *) malloc(set_get_allocated_entries(tab)
* sizeof(set_table_entry));
memsize += set_get_allocated_entries(tab) * sizeof(set_table_entry);
tab->entries = (set_table_entry *)malloc(memsize);
set_make_tab_empty(tab);
tab->rebuilds_num = 0;
return tab;
@ -2499,7 +2525,6 @@ set_table_clear(set_table *tab)
void
set_free_table(set_table *tab)
{
free(tab->bins);
free(tab->entries);
free(tab);
}
@ -2509,7 +2534,7 @@ size_t
set_memsize(const set_table *tab)
{
return(sizeof(set_table)
+ (tab->bins == NULL ? 0 : set_bins_size(tab))
+ (tab->entry_power <= MAX_POWER2_FOR_TABLES_WITHOUT_BINS ? 0 : set_bins_size(tab))
+ set_get_allocated_entries(tab) * sizeof(set_table_entry));
}
@ -2542,7 +2567,7 @@ set_rebuild_table(set_table *tab)
|| tab->num_entries < (1 << MINIMAL_POWER2)) {
/* Compaction: */
tab->num_entries = 0;
if (tab->bins != NULL)
if (set_has_bins(tab))
set_initialize_bins(tab);
set_rebuild_table_with(tab, tab);
}
@ -2572,7 +2597,7 @@ set_rebuild_table_with(set_table *const new_tab, set_table *const tab)
new_entries = new_tab->entries;
ni = 0;
bins = new_tab->bins;
bins = set_bins_ptr(new_tab);
size_ind = set_get_size_ind(new_tab);
st_index_t bound = tab->entries_bound;
set_table_entry *entries = tab->entries;
@ -2602,8 +2627,6 @@ set_rebuild_move_table(set_table *const new_tab, set_table *const tab)
tab->entry_power = new_tab->entry_power;
tab->bin_power = new_tab->bin_power;
tab->size_ind = new_tab->size_ind;
free(tab->bins);
tab->bins = new_tab->bins;
free(tab->entries);
tab->entries = new_tab->entries;
free(new_tab);
@ -2688,7 +2711,7 @@ set_find_table_entry_ind(set_table *tab, st_hash_t hash_value, st_data_t key)
perturb = hash_value;
#endif
for (;;) {
bin = get_bin(tab->bins, set_get_size_ind(tab), ind);
bin = get_bin(set_bins_ptr(tab), set_get_size_ind(tab), ind);
if (! EMPTY_OR_DELETED_BIN_P(bin)) {
DO_PTR_EQUAL_CHECK(tab, &entries[bin - ENTRY_BASE], hash_value, key, eq_p, rebuilt_p);
if (EXPECT(rebuilt_p, 0))
@ -2732,7 +2755,7 @@ set_find_table_bin_ind(set_table *tab, st_hash_t hash_value, st_data_t key)
perturb = hash_value;
#endif
for (;;) {
bin = get_bin(tab->bins, set_get_size_ind(tab), ind);
bin = get_bin(set_bins_ptr(tab), set_get_size_ind(tab), ind);
if (! EMPTY_OR_DELETED_BIN_P(bin)) {
DO_PTR_EQUAL_CHECK(tab, &entries[bin - ENTRY_BASE], hash_value, key, eq_p, rebuilt_p);
if (EXPECT(rebuilt_p, 0))
@ -2773,7 +2796,7 @@ set_find_table_bin_ind_direct(set_table *tab, st_hash_t hash_value, st_data_t ke
perturb = hash_value;
#endif
for (;;) {
bin = get_bin(tab->bins, set_get_size_ind(tab), ind);
bin = get_bin(set_bins_ptr(tab), set_get_size_ind(tab), ind);
if (EMPTY_OR_DELETED_BIN_P(bin))
return ind;
#ifdef QUADRATIC_PROBE
@ -2787,7 +2810,7 @@ set_find_table_bin_ind_direct(set_table *tab, st_hash_t hash_value, st_data_t ke
/* Mark I-th bin of table TAB as empty, in other words not
corresponding to any entry. */
#define MARK_SET_BIN_EMPTY(tab, i) (set_bin((tab)->bins, set_get_size_ind(tab), i, EMPTY_BIN))
#define MARK_SET_BIN_EMPTY(tab, i) (set_bin(set_bins_ptr(tab), set_get_size_ind(tab), i, EMPTY_BIN))
/* Return index of table TAB bin for HASH_VALUE and KEY through
BIN_IND and the pointed value as the function result. Reserve the
@ -2823,7 +2846,7 @@ set_find_table_bin_ptr_and_reserve(set_table *tab, st_hash_t *hash_value,
firset_deleted_bin_ind = UNDEFINED_BIN_IND;
entries = tab->entries;
for (;;) {
entry_index = get_bin(tab->bins, set_get_size_ind(tab), ind);
entry_index = get_bin(set_bins_ptr(tab), set_get_size_ind(tab), ind);
if (EMPTY_BIN_P(entry_index)) {
tab->num_entries++;
entry_index = UNDEFINED_ENTRY_IND;
@ -2863,7 +2886,7 @@ set_table_lookup(set_table *tab, st_data_t key)
st_hash_t hash = set_do_hash(key, tab);
retry:
if (tab->bins == NULL) {
if (!set_has_bins(tab)) {
bin = set_find_entry(tab, hash, key);
if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0))
goto retry;
@ -2907,7 +2930,7 @@ set_insert(set_table *tab, st_data_t key)
hash_value = set_do_hash(key, tab);
retry:
set_rebuild_table_if_necessary(tab);
if (tab->bins == NULL) {
if (!set_has_bins(tab)) {
bin = set_find_entry(tab, hash_value, key);
if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0))
goto retry;
@ -2930,7 +2953,7 @@ set_insert(set_table *tab, st_data_t key)
entry->hash = hash_value;
entry->key = key;
if (bin_ind != UNDEFINED_BIN_IND)
set_bin(tab->bins, set_get_size_ind(tab), bin_ind, ind + ENTRY_BASE);
set_bin(set_bins_ptr(tab), set_get_size_ind(tab), bin_ind, ind + ENTRY_BASE);
return 0;
}
return 1;
@ -2941,18 +2964,9 @@ static set_table *
set_replace(set_table *new_tab, set_table *old_tab)
{
*new_tab = *old_tab;
if (old_tab->bins == NULL)
new_tab->bins = NULL;
else {
new_tab->bins = (st_index_t *) malloc(set_bins_size(old_tab));
}
new_tab->entries = (set_table_entry *) malloc(set_get_allocated_entries(old_tab)
* sizeof(set_table_entry));
MEMCPY(new_tab->entries, old_tab->entries, set_table_entry,
set_get_allocated_entries(old_tab));
if (old_tab->bins != NULL)
MEMCPY(new_tab->bins, old_tab->bins, char, set_bins_size(old_tab));
size_t memsize = set_allocated_entries_size(old_tab) + set_bins_size(old_tab);
new_tab->entries = (set_table_entry *)malloc(memsize);
MEMCPY(new_tab->entries, old_tab->entries, char, memsize);
return new_tab;
}
@ -2991,7 +3005,7 @@ set_update_range_for_deleted(set_table *tab, st_index_t n)
corresponding to deleted entries. */
#define MARK_SET_BIN_DELETED(tab, i) \
do { \
set_bin((tab)->bins, set_get_size_ind(tab), i, DELETED_BIN); \
set_bin(set_bins_ptr(tab), set_get_size_ind(tab), i, DELETED_BIN); \
} while (0)
/* Delete entry with KEY from table TAB, and return non-zero. If
@ -3006,7 +3020,7 @@ set_table_delete(set_table *tab, st_data_t *key)
hash = set_do_hash(*key, tab);
retry:
if (tab->bins == NULL) {
if (!set_has_bins(tab)) {
bin = set_find_entry(tab, hash, *key);
if (EXPECT(bin == REBUILT_TABLE_ENTRY_IND, 0))
goto retry;
@ -3021,7 +3035,7 @@ set_table_delete(set_table *tab, st_data_t *key)
if (bin_ind == UNDEFINED_BIN_IND) {
return 0;
}
bin = get_bin(tab->bins, set_get_size_ind(tab), bin_ind) - ENTRY_BASE;
bin = get_bin(set_bins_ptr(tab), set_get_size_ind(tab), bin_ind) - ENTRY_BASE;
MARK_SET_BIN_DELETED(tab, bin_ind);
}
entry = &tab->entries[bin];
@ -3052,7 +3066,7 @@ set_general_foreach(set_table *tab, set_foreach_check_callback_func *func,
st_index_t i, rebuilds_num;
st_hash_t hash;
st_data_t key;
int error_p, packed_p = tab->bins == NULL;
int error_p, packed_p = !set_has_bins(tab);
entries = tab->entries;
/* The bound can change inside the loop even without rebuilding
@ -3074,7 +3088,7 @@ set_general_foreach(set_table *tab, set_foreach_check_callback_func *func,
if (rebuilds_num != tab->rebuilds_num) {
retry:
entries = tab->entries;
packed_p = tab->bins == NULL;
packed_p = !set_has_bins(tab);
if (packed_p) {
i = set_find_entry(tab, hash, key);
if (EXPECT(i == REBUILT_TABLE_ENTRY_IND, 0))
@ -3122,7 +3136,7 @@ set_general_foreach(set_table *tab, set_foreach_check_callback_func *func,
goto again;
if (bin_ind == UNDEFINED_BIN_IND)
break;
bin = get_bin(tab->bins, set_get_size_ind(tab), bin_ind) - ENTRY_BASE;
bin = get_bin(set_bins_ptr(tab), set_get_size_ind(tab), bin_ind) - ENTRY_BASE;
MARK_SET_BIN_DELETED(tab, bin_ind);
}
curr_entry_ptr = &entries[bin];

View file

@ -103,6 +103,8 @@ class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
end if !openssl?(3, 0, 0)
def test_params_ok?
omit_on_fips
# Skip the tests in old OpenSSL version 1.1.1c or early versions before
# applying the following commits in OpenSSL 1.1.1d to make `DH_check`
# function pass the RFC 7919 FFDHE group texts.

View file

@ -72,6 +72,8 @@ class OpenSSL::TestEC < OpenSSL::PKeyTestCase
end
def test_check_key
omit_on_fips
key0 = Fixtures.pkey("p256")
assert_equal(true, key0.check_key)
assert_equal(true, key0.private?)

View file

@ -49,18 +49,24 @@ class TestRubyOptions < Test::Unit::TestCase
def test_usage
assert_in_out_err(%w(-h)) do |r, e|
assert_operator(r.size, :<=, 25)
longer = r[1..-1].select {|x| x.size >= 80}
assert_operator(r.size, :<=, 26)
longer = r[3..-1].select {|x| x.size >= 80}
assert_equal([], longer)
assert_equal([], e)
version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}"
assert_include(r, "Details and examples at https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html")
end
end
def test_usage_long
assert_in_out_err(%w(--help)) do |r, e|
longer = r[1..-1].select {|x| x.size > 80}
longer = r[3..-1].select {|x| x.size > 80}
assert_equal([], longer)
assert_equal([], e)
version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}"
assert_include(r, "Details and examples at https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html")
end
end

View file

@ -924,6 +924,18 @@ class TC_Set < Test::Unit::TestCase
end
end;
end
def test_larger_sets
set = Set.new
10_000.times do |i|
set << i
end
set = set.dup
10_000.times do |i|
assert_includes set, i
end
end
end
class TC_Enumerable < Test::Unit::TestCase

View file

@ -149,11 +149,14 @@ class TestShapes < Test::Unit::TestCase
def test_too_many_ivs_on_class
obj = Class.new
(MANY_IVS + 1).times do
obj.instance_variable_set(:@test_too_many_ivs_on_class, 1)
refute_predicate RubyVM::Shape.of(obj), :too_complex?
MANY_IVS.times do
obj.instance_variable_set(:"@a#{_1}", 1)
end
assert_false RubyVM::Shape.of(obj).too_complex?
refute_predicate RubyVM::Shape.of(obj), :too_complex?
end
def test_removing_when_too_many_ivs_on_class

View file

@ -2320,6 +2320,46 @@ class TestTranscode < Test::Unit::TestCase
assert_equal("A\nB\nC", s.encode(usascii, newline: :lf))
end
def test_ractor_lazy_load_encoding
assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
begin;
rs = []
autoload_encodings = Encoding.list.select { |e| e.inspect.include?("(autoload)") }.freeze
7.times do
rs << Ractor.new(autoload_encodings) do |encodings|
str = "\u0300"
encodings.each do |enc|
str.encode(enc) rescue Encoding::UndefinedConversionError
end
end
end
while rs.any?
r, _obj = Ractor.select(*rs)
rs.delete(r)
end
assert rs.empty?
end;
end
def test_ractor_lazy_load_encoding_random
assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}")
begin;
rs = []
100.times do
rs << Ractor.new do
"\u0300".encode(Encoding.list.sample) rescue Encoding::UndefinedConversionError
end
end
while rs.any?
r, _obj = Ractor.select(*rs)
rs.delete(r)
end
assert rs.empty?
end;
end
private
def assert_conversion_both_ways_utf8(utf8, raw, encoding)

View file

@ -61,6 +61,30 @@ class TestZJIT < Test::Unit::TestCase
}
end
def test_setglobal
assert_compiles '1', %q{
def test
$a = 1
$a
end
test
}, insns: [:setglobal]
end
def test_setglobal_with_trace_var_exception
assert_compiles '"rescued"', %q{
def test
$a = 1
rescue
"rescued"
end
trace_var(:$a) { raise }
test
}, insns: [:setglobal]
end
def test_setlocal
assert_compiles '3', %q{
def test(n)

View file

@ -340,7 +340,7 @@ transcode_search_path(const char *sname, const char *dname,
bfs.queue_last_ptr = &q->next;
bfs.queue = q;
bfs.visited = st_init_strcasetable();
bfs.visited = st_init_strcasetable(); // due to base encodings, we need to do search in a loop
st_add_direct(bfs.visited, (st_data_t)sname, (st_data_t)NULL);
RB_VM_LOCKING() {
@ -351,14 +351,14 @@ transcode_search_path(const char *sname, const char *dname,
bfs.queue_last_ptr = &bfs.queue;
}
lookup_res = st_lookup(transcoder_table, (st_data_t)q->enc, &val);
lookup_res = st_lookup(transcoder_table, (st_data_t)q->enc, &val); // src => table2
if (!lookup_res) {
xfree(q);
continue;
}
table2 = (st_table *)val;
if (st_lookup(table2, (st_data_t)dname, &val)) {
if (st_lookup(table2, (st_data_t)dname, &val)) { // dest => econv
st_add_direct(bfs.visited, (st_data_t)dname, (st_data_t)q->enc);
xfree(q);
found = true;
@ -411,8 +411,7 @@ int rb_require_internal_silent(VALUE fname);
static const rb_transcoder *
load_transcoder_entry(transcoder_entry_t *entry)
{
// changes result of entry->transcoder depending on if it's required or not, so needs lock
ASSERT_vm_locking();
ASSERT_vm_unlocking();
if (entry->transcoder)
return entry->transcoder;
@ -427,7 +426,7 @@ load_transcoder_entry(transcoder_entry_t *entry)
memcpy(path + sizeof(transcoder_lib_prefix) - 1, lib, len);
rb_str_set_len(fn, total_len);
OBJ_FREEZE(fn);
rb_require_internal_silent(fn);
rb_require_internal_silent(fn); // Sets entry->transcoder
}
if (entry->transcoder)
@ -981,7 +980,6 @@ rb_econv_open_by_transcoder_entries(int n, transcoder_entry_t **entries)
{
rb_econv_t *ec;
int i, ret;
ASSERT_vm_locking();
for (i = 0; i < n; i++) {
const rb_transcoder *tr;
@ -1026,10 +1024,8 @@ rb_econv_open0(const char *sname, const char *dname, int ecflags)
transcoder_entry_t **entries = NULL;
int num_trans;
rb_econv_t *ec;
ASSERT_vm_locking();
/* Just check if sname and dname are defined */
/* (This check is needed?) */
// loads encodings if not loaded already
if (*sname) rb_enc_find_index(sname);
if (*dname) rb_enc_find_index(dname);
@ -1117,15 +1113,13 @@ rb_econv_open(const char *sname, const char *dname, int ecflags)
if (num_decorators == -1)
return NULL;
RB_VM_LOCKING() {
ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK);
if (ec) {
for (i = 0; i < num_decorators; i++) {
if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) {
rb_econv_close(ec);
ec = NULL;
break;
}
ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK);
if (ec) {
for (i = 0; i < num_decorators; i++) {
if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) {
rb_econv_close(ec);
ec = NULL;
break;
}
}
}
@ -1960,12 +1954,9 @@ rb_econv_add_converter(rb_econv_t *ec, const char *sname, const char *dname, int
if (ec->started != 0)
return -1;
RB_VM_LOCKING() {
entry = get_transcoder_entry(sname, dname);
if (entry) {
tr = load_transcoder_entry(entry);
}
entry = get_transcoder_entry(sname, dname);
if (entry) {
tr = load_transcoder_entry(entry);
}
return tr ? rb_econv_add_transcoder_at(ec, tr, n) : -1;
@ -2681,21 +2672,19 @@ rb_econv_open_opts(const char *source_encoding, const char *destination_encoding
replacement = rb_hash_aref(opthash, sym_replace);
}
RB_VM_LOCKING() {
ec = rb_econv_open(source_encoding, destination_encoding, ecflags);
if (ec) {
if (!NIL_P(replacement)) {
int ret;
rb_encoding *enc = rb_enc_get(replacement);
ec = rb_econv_open(source_encoding, destination_encoding, ecflags);
if (ec) {
if (!NIL_P(replacement)) {
int ret;
rb_encoding *enc = rb_enc_get(replacement);
ret = rb_econv_set_replacement(ec,
(const unsigned char *)RSTRING_PTR(replacement),
RSTRING_LEN(replacement),
rb_enc_name(enc));
if (ret == -1) {
rb_econv_close(ec);
ec = NULL;
}
ret = rb_econv_set_replacement(ec,
(const unsigned char *)RSTRING_PTR(replacement),
RSTRING_LEN(replacement),
rb_enc_name(enc));
if (ret == -1) {
rb_econv_close(ec);
ec = NULL;
}
}
}
@ -3132,10 +3121,8 @@ decorate_convpath(VALUE convpath, int ecflags)
const char *dname = rb_enc_name(rb_to_encoding(RARRAY_AREF(pair, 1)));
transcoder_entry_t *entry;
const rb_transcoder *tr;
RB_VM_LOCKING() {
entry = get_transcoder_entry(sname, dname);
tr = load_transcoder_entry(entry);
}
entry = get_transcoder_entry(sname, dname);
tr = load_transcoder_entry(entry);
if (!tr)
return -1;
if (!DECORATOR_P(tr->src_encoding, tr->dst_encoding) &&

View file

@ -1231,16 +1231,33 @@ rb_obj_fields(VALUE obj, ID field_name)
VALUE fields_obj = 0;
if (rb_shape_obj_has_fields(obj)) {
switch (BUILTIN_TYPE(obj)) {
case T_DATA:
if (LIKELY(RTYPEDDATA_P(obj))) {
fields_obj = RTYPEDDATA(obj)->fields_obj;
break;
}
goto generic_fields;
case T_STRUCT:
if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) {
fields_obj = RSTRUCT_FIELDS_OBJ(obj);
break;
}
// fall through
goto generic_fields;
default:
RB_VM_LOCKING() {
if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) {
rb_bug("Object is missing entry in generic_fields_tbl");
generic_fields:
{
rb_execution_context_t *ec = GET_EC();
if (ec->gen_fields_cache.obj == obj) {
fields_obj = ec->gen_fields_cache.fields_obj;
}
else {
RB_VM_LOCKING() {
if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) {
rb_bug("Object is missing entry in generic_fields_tbl");
}
}
ec->gen_fields_cache.fields_obj = fields_obj;
ec->gen_fields_cache.obj = obj;
}
}
}
@ -1254,15 +1271,29 @@ rb_free_generic_ivar(VALUE obj)
if (rb_obj_exivar_p(obj)) {
st_data_t key = (st_data_t)obj, value;
switch (BUILTIN_TYPE(obj)) {
case T_DATA:
if (LIKELY(RTYPEDDATA_P(obj))) {
RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, 0);
break;
}
goto generic_fields;
case T_STRUCT:
if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) {
RSTRUCT_SET_FIELDS_OBJ(obj, 0);
break;
}
// fall through
goto generic_fields;
default:
RB_VM_LOCKING() {
st_delete(generic_fields_tbl_no_ractor_check(), &key, &value);
generic_fields:
{
rb_execution_context_t *ec = GET_EC();
if (ec->gen_fields_cache.obj == obj) {
ec->gen_fields_cache.obj = Qundef;
ec->gen_fields_cache.fields_obj = Qundef;
}
RB_VM_LOCKING() {
st_delete(generic_fields_tbl_no_ractor_check(), &key, &value);
}
}
}
RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID);
@ -1279,17 +1310,32 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie
if (fields_obj != original_fields_obj) {
switch (BUILTIN_TYPE(obj)) {
case T_DATA:
if (LIKELY(RTYPEDDATA_P(obj))) {
RB_OBJ_WRITE(obj, &RTYPEDDATA(obj)->fields_obj, fields_obj);
break;
}
goto generic_fields;
case T_STRUCT:
if (LIKELY(!FL_TEST_RAW(obj, RSTRUCT_GEN_FIELDS))) {
RSTRUCT_SET_FIELDS_OBJ(obj, fields_obj);
break;
}
// fall through
goto generic_fields;
default:
RB_VM_LOCKING() {
st_insert(generic_fields_tbl_, (st_data_t)obj, (st_data_t)fields_obj);
generic_fields:
{
RB_VM_LOCKING() {
st_insert(generic_fields_tbl_, (st_data_t)obj, (st_data_t)fields_obj);
}
RB_OBJ_WRITTEN(obj, original_fields_obj, fields_obj);
rb_execution_context_t *ec = GET_EC();
if (ec->gen_fields_cache.fields_obj != fields_obj) {
ec->gen_fields_cache.obj = obj;
ec->gen_fields_cache.fields_obj = fields_obj;
}
}
RB_OBJ_WRITTEN(obj, original_fields_obj, fields_obj);
}
if (original_fields_obj) {
@ -1666,10 +1712,10 @@ imemo_fields_complex_from_obj_i(ID key, VALUE val, st_data_t arg)
}
static VALUE
imemo_fields_complex_from_obj(VALUE klass, VALUE source_fields_obj, shape_id_t shape_id)
imemo_fields_complex_from_obj(VALUE owner, VALUE source_fields_obj, shape_id_t shape_id)
{
attr_index_t len = source_fields_obj ? RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj)) : 0;
VALUE fields_obj = rb_imemo_fields_new_complex(klass, len + 1);
VALUE fields_obj = rb_imemo_fields_new_complex(owner, len + 1);
rb_field_foreach(source_fields_obj, imemo_fields_complex_from_obj_i, (st_data_t)fields_obj, false);
RBASIC_SET_SHAPE_ID(fields_obj, shape_id);
@ -1678,9 +1724,9 @@ imemo_fields_complex_from_obj(VALUE klass, VALUE source_fields_obj, shape_id_t s
}
static VALUE
imemo_fields_copy_capa(VALUE klass, VALUE source_fields_obj, attr_index_t new_size)
imemo_fields_copy_capa(VALUE owner, VALUE source_fields_obj, attr_index_t new_size)
{
VALUE fields_obj = rb_imemo_fields_new(klass, new_size);
VALUE fields_obj = rb_imemo_fields_new(owner, new_size);
if (source_fields_obj) {
attr_index_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj));
VALUE *fields = rb_imemo_fields_ptr(fields_obj);
@ -1832,7 +1878,7 @@ general_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val, void *data,
}
static VALUE
imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID field_name, VALUE val, bool concurrent)
imemo_fields_set(VALUE owner, VALUE fields_obj, shape_id_t target_shape_id, ID field_name, VALUE val, bool concurrent)
{
const VALUE original_fields_obj = fields_obj;
shape_id_t current_shape_id = fields_obj ? RBASIC_SHAPE_ID(fields_obj) : ROOT_SHAPE_ID;
@ -1847,7 +1893,7 @@ imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID f
}
}
else {
fields_obj = imemo_fields_complex_from_obj(klass, original_fields_obj, target_shape_id);
fields_obj = imemo_fields_complex_from_obj(owner, original_fields_obj, target_shape_id);
current_shape_id = target_shape_id;
}
@ -1861,7 +1907,7 @@ imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID f
else {
attr_index_t index = RSHAPE_INDEX(target_shape_id);
if (concurrent || index >= RSHAPE_CAPACITY(current_shape_id)) {
fields_obj = imemo_fields_copy_capa(klass, original_fields_obj, RSHAPE_CAPACITY(target_shape_id));
fields_obj = imemo_fields_copy_capa(owner, original_fields_obj, RSHAPE_CAPACITY(target_shape_id));
}
VALUE *table = rb_imemo_fields_ptr(fields_obj);
@ -1884,7 +1930,7 @@ generic_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE va
}
const VALUE original_fields_obj = rb_obj_fields(obj, field_name);
VALUE fields_obj = imemo_fields_set(rb_obj_class(obj), original_fields_obj, target_shape_id, field_name, val, false);
VALUE fields_obj = imemo_fields_set(obj, original_fields_obj, target_shape_id, field_name, val, false);
rb_obj_set_fields(obj, fields_obj, field_name, original_fields_obj);
}
@ -2319,7 +2365,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
return;
}
new_fields_obj = rb_imemo_fields_new(rb_obj_class(dest), RSHAPE_CAPACITY(dest_shape_id));
new_fields_obj = rb_imemo_fields_new(dest, RSHAPE_CAPACITY(dest_shape_id));
VALUE *src_buf = rb_imemo_fields_ptr(fields_obj);
VALUE *dest_buf = rb_imemo_fields_ptr(new_fields_obj);
rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, src_buf, src_shape_id);
@ -4619,7 +4665,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc
{
bool existing = true;
const VALUE original_fields_obj = fields_obj;
fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(rb_singleton_class(klass), 1);
fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(klass, 1);
shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj);
shape_id_t next_shape_id = current_shape_id;
@ -4639,7 +4685,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc
next_shape_id = rb_shape_transition_add_ivar(fields_obj, id);
if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) {
fields_obj = imemo_fields_complex_from_obj(rb_singleton_class(klass), fields_obj, next_shape_id);
fields_obj = imemo_fields_complex_from_obj(klass, fields_obj, next_shape_id);
goto too_complex;
}
@ -4649,7 +4695,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc
if (next_capacity > current_capacity) {
// We allocate a new fields_obj even when concurrency isn't a concern
// so that we're embedded as long as possible.
fields_obj = imemo_fields_copy_capa(rb_singleton_class(klass), fields_obj, next_capacity);
fields_obj = imemo_fields_copy_capa(klass, fields_obj, next_capacity);
}
RUBY_ASSERT(RSHAPE(next_shape_id)->type == SHAPE_IVAR);

6
vm.c
View file

@ -3441,6 +3441,9 @@ rb_execution_context_update(rb_execution_context_t *ec)
}
ec->storage = rb_gc_location(ec->storage);
ec->gen_fields_cache.obj = rb_gc_location(ec->gen_fields_cache.obj);
ec->gen_fields_cache.fields_obj = rb_gc_location(ec->gen_fields_cache.fields_obj);
}
static enum rb_id_table_iterator_result
@ -3505,6 +3508,9 @@ rb_execution_context_mark(const rb_execution_context_t *ec)
rb_gc_mark(ec->private_const_reference);
rb_gc_mark_movable(ec->storage);
rb_gc_mark_weak((VALUE *)&ec->gen_fields_cache.obj);
rb_gc_mark_weak((VALUE *)&ec->gen_fields_cache.fields_obj);
}
void rb_fiber_mark_self(rb_fiber_t *fib);

View file

@ -1070,6 +1070,11 @@ struct rb_execution_context_struct {
VALUE private_const_reference;
struct {
VALUE obj;
VALUE fields_obj;
} gen_fields_cache;
/* for GC */
struct {
VALUE *stack_start;

View file

@ -5,7 +5,7 @@ use std::ffi::{c_int, c_void};
use crate::asm::Label;
use crate::backend::current::{Reg, ALLOC_REGS};
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption};
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr};
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqStatus};
use crate::state::ZJITState;
use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::compile_time_ns};
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
@ -136,7 +136,7 @@ fn gen_iseq_entry_point_body(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<CodePt
// Remember the block address to reuse it later
let payload = get_or_create_iseq_payload(iseq);
payload.start_ptr = Some(start_ptr);
payload.status = IseqStatus::Compiled(start_ptr);
append_gc_offsets(iseq, &gc_offsets);
// Return a JIT code address
@ -213,23 +213,29 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt
fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<(CodePtr, Vec<(Rc<Branch>, IseqPtr)>)> {
// Return an existing pointer if it's already compiled
let payload = get_or_create_iseq_payload(iseq);
if let Some(start_ptr) = payload.start_ptr {
return Some((start_ptr, vec![]));
match payload.status {
IseqStatus::Compiled(start_ptr) => return Some((start_ptr, vec![])),
IseqStatus::CantCompile => return None,
IseqStatus::NotCompiled => {},
}
// Convert ISEQ into High-level IR and optimize HIR
let function = match compile_iseq(iseq) {
Some(function) => function,
None => return None,
None => {
payload.status = IseqStatus::CantCompile;
return None;
}
};
// Compile the High-level IR
let result = gen_function(cb, iseq, &function);
if let Some((start_ptr, gc_offsets, jit)) = result {
payload.start_ptr = Some(start_ptr);
payload.status = IseqStatus::Compiled(start_ptr);
append_gc_offsets(iseq, &gc_offsets);
Some((start_ptr, jit.branch_iseqs))
} else {
payload.status = IseqStatus::CantCompile;
None
}
}
@ -361,7 +367,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::PatchPoint { invariant, state } => return gen_patch_point(jit, asm, invariant, &function.frame_state(*state)),
Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(asm, *cfun, opnds!(args))?,
Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id),
Insn::SetGlobal { id, val, state: _ } => return Some(gen_setglobal(asm, *id, opnd!(val))),
Insn::SetGlobal { id, val, state } => return gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state)),
Insn::GetGlobal { id, state: _ } => gen_getglobal(asm, *id),
&Insn::GetLocal { ep_offset, level } => gen_getlocal_with_ep(asm, ep_offset, level)?,
Insn::SetLocal { val, ep_offset, level } => return gen_setlocal_with_ep(asm, opnd!(val), *ep_offset, *level),
@ -586,8 +592,12 @@ fn gen_getglobal(asm: &mut Assembler, id: ID) -> Opnd {
}
/// Set global variables
fn gen_setglobal(asm: &mut Assembler, id: ID, val: Opnd) {
fn gen_setglobal(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, state: &FrameState) -> Option<()> {
// When trace_var is used, setting a global variable can cause exceptions
gen_prepare_non_leaf_call(jit, asm, state)?;
asm_ccall!(asm, rb_gvar_set, id.0.into(), val);
Some(())
}
/// Side-exit into the interpreter
@ -1278,6 +1288,7 @@ fn compile_iseq(iseq: IseqPtr) -> Option<Function> {
if !get_option!(disable_hir_opt) {
function.optimize();
}
#[cfg(debug_assertions)]
if let Err(err) = function.validate() {
debug!("ZJIT: compile_iseq: {err:?}");
return None;
@ -1356,27 +1367,40 @@ macro_rules! c_callable {
pub(crate) use c_callable;
c_callable! {
/// Generated code calls this function with the SysV calling convention.
/// See [gen_function_stub].
/// Generated code calls this function with the SysV calling convention. See [gen_function_stub].
/// This function is expected to be called repeatedly when ZJIT fails to compile the stub.
/// We should be able to compile most (if not all) function stubs by side-exiting at unsupported
/// instructions, so this should be used primarily for cb.has_dropped_bytes() situations.
fn function_stub_hit(iseq: IseqPtr, branch_ptr: *const c_void, ec: EcPtr, sp: *mut VALUE) -> *const u8 {
with_vm_lock(src_loc!(), || {
// Get a pointer to compiled code or the side-exit trampoline
let cb = ZJITState::get_code_block();
let code_ptr = with_time_stat(compile_time_ns, || function_stub_hit_body(cb, iseq, branch_ptr));
let code_ptr = if let Some(code_ptr) = code_ptr {
code_ptr
} else {
// gen_push_frame() doesn't set PC and SP, so we need to set them for side-exit
// TODO: We could generate code that sets PC/SP. Note that we'd still need to handle OOM.
/// gen_push_frame() doesn't set PC and SP, so we need to set them before exit
fn set_pc_and_sp(iseq: IseqPtr, ec: EcPtr, sp: *mut VALUE) {
let cfp = unsafe { get_ec_cfp(ec) };
let pc = unsafe { rb_iseq_pc_at_idx(iseq, 0) }; // TODO: handle opt_pc once supported
unsafe { rb_set_cfp_pc(cfp, pc) };
unsafe { rb_set_cfp_sp(cfp, sp) };
}
// If we already know we can't compile the ISEQ, fail early without cb.mark_all_executable().
// TODO: Alan thinks the payload status part of this check can happen without the VM lock, since the whole
// code path can be made read-only. But you still need the check as is while holding the VM lock in any case.
let cb = ZJITState::get_code_block();
let payload = get_or_create_iseq_payload(iseq);
if cb.has_dropped_bytes() || payload.status == IseqStatus::CantCompile {
// Exit to the interpreter
set_pc_and_sp(iseq, ec, sp);
return ZJITState::get_stub_exit().raw_ptr(cb);
}
// Otherwise, attempt to compile the ISEQ. We have to mark_all_executable() beyond this point.
let code_ptr = with_time_stat(compile_time_ns, || function_stub_hit_body(cb, iseq, branch_ptr));
let code_ptr = if let Some(code_ptr) = code_ptr {
code_ptr
} else {
// Exit to the interpreter
set_pc_and_sp(iseq, ec, sp);
ZJITState::get_stub_exit()
};
cb.mark_all_executable();
code_ptr.raw_ptr(cb)
})

View file

@ -7,12 +7,12 @@ use crate::stats::Counter::gc_time_ns;
/// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC.
#[derive(Debug)]
pub struct IseqPayload {
/// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled.
pub status: IseqStatus,
/// Type information of YARV instruction operands
pub profile: IseqProfile,
/// JIT code address of the first block
pub start_ptr: Option<CodePtr>,
/// GC offsets of the JIT code. These are the addresses of objects that need to be marked.
pub gc_offsets: Vec<CodePtr>,
}
@ -20,13 +20,21 @@ pub struct IseqPayload {
impl IseqPayload {
fn new(iseq_size: u32) -> Self {
Self {
status: IseqStatus::NotCompiled,
profile: IseqProfile::new(iseq_size),
start_ptr: None,
gc_offsets: vec![],
}
}
}
#[derive(Debug, PartialEq)]
pub enum IseqStatus {
/// CodePtr has the JIT code address of the first block
Compiled(CodePtr),
CantCompile,
NotCompiled,
}
/// Get a pointer to the payload object associated with an ISEQ. Create one if none exists.
pub fn get_or_create_iseq_payload_ptr(iseq: IseqPtr) -> *mut IseqPayload {
type VoidPtr = *mut c_void;

View file

@ -604,7 +604,9 @@ impl Insn {
Insn::Param { .. } => false,
Insn::StringCopy { .. } => false,
Insn::NewArray { .. } => false,
Insn::NewHash { .. } => false,
// NewHash's operands may be hashed and compared for equality, which could have
// side-effects.
Insn::NewHash { elements, .. } => elements.len() > 0,
Insn::NewRange { .. } => false,
Insn::ArrayDup { .. } => false,
Insn::HashDup { .. } => false,
@ -6089,7 +6091,7 @@ mod opt_tests {
}
#[test]
fn test_eliminate_new_hash_with_elements() {
fn test_no_eliminate_new_hash_with_elements() {
eval("
def test(aval, bval)
c = {a: aval, b: bval}
@ -6099,6 +6101,10 @@ mod opt_tests {
assert_optimized_method_hir("test", expect![[r#"
fn test@<compiled>:3:
bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject):
v3:NilClass = Const Value(nil)
v5:StaticSymbol[:a] = Const Value(VALUE(0x1000))
v6:StaticSymbol[:b] = Const Value(VALUE(0x1008))
v8:HashExact = NewHash v5: v1, v6: v2
v9:Fixnum[5] = Const Value(5)
Return v9
"#]]);