mirror of
https://github.com/ruby/ruby.git
synced 2025-09-15 08:33:58 +02:00
Merge branch 'master' into zjit-env
This commit is contained in:
commit
9fdf29cc89
35 changed files with 438 additions and 635 deletions
2
depend
2
depend
|
@ -14598,7 +14598,6 @@ 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_string.h
|
||||||
ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h
|
ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strncasecmp.h
|
||||||
ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h
|
ruby.$(OBJEXT): $(top_srcdir)/prism/util/pm_strpbrk.h
|
||||||
ruby.$(OBJEXT): $(top_srcdir)/version.h
|
|
||||||
ruby.$(OBJEXT): {$(VPATH)}assert.h
|
ruby.$(OBJEXT): {$(VPATH)}assert.h
|
||||||
ruby.$(OBJEXT): {$(VPATH)}atomic.h
|
ruby.$(OBJEXT): {$(VPATH)}atomic.h
|
||||||
ruby.$(OBJEXT): {$(VPATH)}backward/2/assume.h
|
ruby.$(OBJEXT): {$(VPATH)}backward/2/assume.h
|
||||||
|
@ -14782,7 +14781,6 @@ ruby.$(OBJEXT): {$(VPATH)}prism/ast.h
|
||||||
ruby.$(OBJEXT): {$(VPATH)}prism/diagnostic.h
|
ruby.$(OBJEXT): {$(VPATH)}prism/diagnostic.h
|
||||||
ruby.$(OBJEXT): {$(VPATH)}prism/version.h
|
ruby.$(OBJEXT): {$(VPATH)}prism/version.h
|
||||||
ruby.$(OBJEXT): {$(VPATH)}prism_compile.h
|
ruby.$(OBJEXT): {$(VPATH)}prism_compile.h
|
||||||
ruby.$(OBJEXT): {$(VPATH)}revision.h
|
|
||||||
ruby.$(OBJEXT): {$(VPATH)}ruby.c
|
ruby.$(OBJEXT): {$(VPATH)}ruby.c
|
||||||
ruby.$(OBJEXT): {$(VPATH)}ruby_assert.h
|
ruby.$(OBJEXT): {$(VPATH)}ruby_assert.h
|
||||||
ruby.$(OBJEXT): {$(VPATH)}ruby_atomic.h
|
ruby.$(OBJEXT): {$(VPATH)}ruby_atomic.h
|
||||||
|
|
|
@ -302,7 +302,7 @@ $ ruby -n -e 'p $_' desiderata.txt
|
||||||
"be on good terms with all persons.\n"
|
"be on good terms with all persons.\n"
|
||||||
```
|
```
|
||||||
|
|
||||||
With option `-l' (chopped):
|
With option `-l` (chopped):
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ ruby -ln -e 'p $_' desiderata.txt
|
$ ruby -ln -e 'p $_' desiderata.txt
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
require 'mkmf'
|
require 'mkmf'
|
||||||
if RUBY_ENGINE == "ruby" and have_library('iphlpapi', 'GetNetworkParams')
|
if RUBY_ENGINE == "ruby" and have_library('iphlpapi', 'GetNetworkParams', ['windows.h', 'iphlpapi.h'])
|
||||||
create_makefile('win32/resolv')
|
create_makefile('win32/resolv')
|
||||||
else
|
else
|
||||||
File.write('Makefile', "all clean install:\n\t@echo Done: $(@)\n")
|
File.write('Makefile', "all clean install:\n\t@echo Done: $(@)\n")
|
||||||
|
|
16
gc.c
16
gc.c
|
@ -1921,7 +1921,7 @@ object_id(VALUE obj)
|
||||||
// in fields.
|
// in fields.
|
||||||
return class_object_id(obj);
|
return class_object_id(obj);
|
||||||
case T_IMEMO:
|
case T_IMEMO:
|
||||||
rb_bug("T_IMEMO can't have an object_id");
|
RUBY_ASSERT(IMEMO_TYPE_P(obj, imemo_fields));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -1945,20 +1945,26 @@ build_id2ref_i(VALUE obj, void *data)
|
||||||
switch (BUILTIN_TYPE(obj)) {
|
switch (BUILTIN_TYPE(obj)) {
|
||||||
case T_CLASS:
|
case T_CLASS:
|
||||||
case T_MODULE:
|
case T_MODULE:
|
||||||
|
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
|
||||||
if (RCLASS(obj)->object_id) {
|
if (RCLASS(obj)->object_id) {
|
||||||
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
|
|
||||||
st_insert(id2ref_tbl, RCLASS(obj)->object_id, obj);
|
st_insert(id2ref_tbl, RCLASS(obj)->object_id, obj);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case T_IMEMO:
|
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;
|
break;
|
||||||
default:
|
case T_OBJECT:
|
||||||
|
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
|
||||||
if (rb_shape_obj_has_id(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);
|
st_insert(id2ref_tbl, rb_obj_id(obj), obj);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
// For generic_fields, the T_IMEMO/fields is responsible for populating the entry.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
63
gc.rb
63
gc.rb
|
@ -383,11 +383,29 @@ module GC
|
||||||
end
|
end
|
||||||
|
|
||||||
# call-seq:
|
# call-seq:
|
||||||
# GC.measure_total_time = true/false
|
# GC.measure_total_time = setting -> setting
|
||||||
#
|
#
|
||||||
# Enables measuring \GC time.
|
# Enables or disables \GC total time measurement;
|
||||||
# You can get the result with <tt>GC.stat(:time)</tt>.
|
# returns +setting+.
|
||||||
# Note that \GC time measurement can cause some performance overhead.
|
# See GC.total_time.
|
||||||
|
#
|
||||||
|
# When argument +object+ is +nil+ or +false+, disables total time measurement;
|
||||||
|
# GC.measure_total_time then returns +false+:
|
||||||
|
#
|
||||||
|
# GC.measure_total_time = nil # => nil
|
||||||
|
# GC.measure_total_time # => false
|
||||||
|
# GC.measure_total_time = false # => false
|
||||||
|
# GC.measure_total_time # => false
|
||||||
|
#
|
||||||
|
# Otherwise, enables total time measurement;
|
||||||
|
# GC.measure_total_time then returns +true+:
|
||||||
|
#
|
||||||
|
# GC.measure_total_time = true # => true
|
||||||
|
# GC.measure_total_time # => true
|
||||||
|
# GC.measure_total_time = :foo # => :foo
|
||||||
|
# GC.measure_total_time # => true
|
||||||
|
#
|
||||||
|
# Note that when enabled, total time measurement affects performance.
|
||||||
def self.measure_total_time=(flag)
|
def self.measure_total_time=(flag)
|
||||||
Primitive.cstmt! %{
|
Primitive.cstmt! %{
|
||||||
rb_gc_impl_set_measure_total_time(rb_gc_get_objspace(), flag);
|
rb_gc_impl_set_measure_total_time(rb_gc_get_objspace(), flag);
|
||||||
|
@ -396,10 +414,11 @@ module GC
|
||||||
end
|
end
|
||||||
|
|
||||||
# call-seq:
|
# call-seq:
|
||||||
# GC.measure_total_time -> true/false
|
# GC.measure_total_time -> true or false
|
||||||
#
|
#
|
||||||
# Returns the measure_total_time flag (default: +true+).
|
# Returns the setting for \GC total time measurement;
|
||||||
# Note that measurement can affect the application's performance.
|
# the initial setting is +true+.
|
||||||
|
# See GC.total_time.
|
||||||
def self.measure_total_time
|
def self.measure_total_time
|
||||||
Primitive.cexpr! %{
|
Primitive.cexpr! %{
|
||||||
RBOOL(rb_gc_impl_get_measure_total_time(rb_gc_get_objspace()))
|
RBOOL(rb_gc_impl_get_measure_total_time(rb_gc_get_objspace()))
|
||||||
|
@ -407,9 +426,35 @@ module GC
|
||||||
end
|
end
|
||||||
|
|
||||||
# call-seq:
|
# call-seq:
|
||||||
# GC.total_time -> int
|
# GC.total_time -> integer
|
||||||
|
#
|
||||||
|
# Returns the \GC total time in nanoseconds:
|
||||||
|
#
|
||||||
|
# GC.total_time # => 156250
|
||||||
|
#
|
||||||
|
# Note that total time accumulates
|
||||||
|
# only when total time measurement is enabled
|
||||||
|
# (that is, when GC.measure_total_time is +true+):
|
||||||
|
#
|
||||||
|
# GC.measure_total_time # => true
|
||||||
|
# GC.total_time # => 625000
|
||||||
|
# GC.start
|
||||||
|
# GC.total_time # => 937500
|
||||||
|
# GC.start
|
||||||
|
# GC.total_time # => 1093750
|
||||||
|
#
|
||||||
|
# GC.measure_total_time = false
|
||||||
|
# GC.total_time # => 1250000
|
||||||
|
# GC.start
|
||||||
|
# GC.total_time # => 1250000
|
||||||
|
# GC.start
|
||||||
|
# GC.total_time # => 1250000
|
||||||
|
#
|
||||||
|
# GC.measure_total_time = true
|
||||||
|
# GC.total_time # => 1250000
|
||||||
|
# GC.start
|
||||||
|
# GC.total_time # => 1406250
|
||||||
#
|
#
|
||||||
# Returns the measured \GC total time in nanoseconds.
|
|
||||||
def self.total_time
|
def self.total_time
|
||||||
Primitive.cexpr! %{
|
Primitive.cexpr! %{
|
||||||
ULL2NUM(rb_gc_impl_get_total_time(rb_gc_get_objspace()))
|
ULL2NUM(rb_gc_impl_get_total_time(rb_gc_get_objspace()))
|
||||||
|
|
|
@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct
|
||||||
pstore 0.2.0 https://github.com/ruby/pstore
|
pstore 0.2.0 https://github.com/ruby/pstore
|
||||||
benchmark 0.4.1 https://github.com/ruby/benchmark
|
benchmark 0.4.1 https://github.com/ruby/benchmark
|
||||||
logger 1.7.0 https://github.com/ruby/logger
|
logger 1.7.0 https://github.com/ruby/logger
|
||||||
rdoc 6.14.2 https://github.com/ruby/rdoc f4a90c6010b2346cb5426d4496f5a37a136a82fb # for markdown
|
rdoc 6.14.2 https://github.com/ruby/rdoc
|
||||||
win32ole 1.9.2 https://github.com/ruby/win32ole
|
win32ole 1.9.2 https://github.com/ruby/win32ole
|
||||||
irb 1.15.2 https://github.com/ruby/irb 331c4e851296b115db766c291e8cf54a2492fb36
|
irb 1.15.2 https://github.com/ruby/irb 331c4e851296b115db766c291e8cf54a2492fb36
|
||||||
reline 0.6.2 https://github.com/ruby/reline
|
reline 0.6.2 https://github.com/ruby/reline
|
||||||
|
|
26
imemo.c
26
imemo.c
|
@ -109,16 +109,16 @@ rb_imemo_tmpbuf_parser_heap(void *buf, rb_imemo_tmpbuf_t *old_heap, size_t cnt)
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
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);
|
size_t embedded_size = offsetof(struct rb_fields, as.embed) + capa * sizeof(VALUE);
|
||||||
if (rb_gc_size_allocatable_p(embedded_size)) {
|
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));
|
RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields));
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
else {
|
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);
|
FL_SET_RAW(fields, OBJ_FIELD_EXTERNAL);
|
||||||
IMEMO_OBJ_FIELDS(fields)->as.external.ptr = ALLOC_N(VALUE, capa);
|
IMEMO_OBJ_FIELDS(fields)->as.external.ptr = ALLOC_N(VALUE, capa);
|
||||||
return fields;
|
return fields;
|
||||||
|
@ -126,23 +126,23 @@ imemo_fields_new(VALUE klass, size_t capa)
|
||||||
}
|
}
|
||||||
|
|
||||||
VALUE
|
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
|
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);
|
IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa);
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
VALUE
|
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
|
static int
|
||||||
|
@ -161,9 +161,9 @@ imemo_fields_complex_wb_i(st_data_t key, st_data_t value, st_data_t arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
VALUE
|
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;
|
IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl;
|
||||||
st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields);
|
st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields);
|
||||||
return fields;
|
return fields;
|
||||||
|
@ -176,7 +176,7 @@ rb_imemo_fields_clone(VALUE fields_obj)
|
||||||
VALUE clone;
|
VALUE clone;
|
||||||
|
|
||||||
if (rb_shape_too_complex_p(shape_id)) {
|
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);
|
RBASIC_SET_SHAPE_ID(clone, shape_id);
|
||||||
st_table *src_table = rb_imemo_fields_complex_tbl(fields_obj);
|
st_table *src_table = rb_imemo_fields_complex_tbl(fields_obj);
|
||||||
st_table *dest_table = rb_imemo_fields_complex_tbl(clone);
|
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);
|
st_foreach(dest_table, imemo_fields_complex_wb_i, (st_data_t)clone);
|
||||||
}
|
}
|
||||||
else {
|
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);
|
RBASIC_SET_SHAPE_ID(clone, shape_id);
|
||||||
VALUE *fields = rb_imemo_fields_ptr(clone);
|
VALUE *fields = rb_imemo_fields_ptr(clone);
|
||||||
attr_index_t fields_count = RSHAPE_LEN(shape_id);
|
attr_index_t fields_count = RSHAPE_LEN(shape_id);
|
||||||
|
|
|
@ -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));
|
RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE));
|
||||||
rb_classext_t *ext = RCLASS_EXT_WRITABLE(obj);
|
rb_classext_t *ext = RCLASS_EXT_WRITABLE(obj);
|
||||||
if (!ext->fields_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;
|
return ext->fields_obj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,12 +273,18 @@ struct rb_fields {
|
||||||
#define OBJ_FIELD_EXTERNAL IMEMO_FL_USER0
|
#define OBJ_FIELD_EXTERNAL IMEMO_FL_USER0
|
||||||
#define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields)
|
#define IMEMO_OBJ_FIELDS(fields) ((struct rb_fields *)fields)
|
||||||
|
|
||||||
VALUE rb_imemo_fields_new(VALUE klass, size_t capa);
|
VALUE rb_imemo_fields_new(VALUE owner, size_t capa);
|
||||||
VALUE rb_imemo_fields_new_complex(VALUE klass, size_t capa);
|
VALUE rb_imemo_fields_new_complex(VALUE owner, size_t capa);
|
||||||
VALUE rb_imemo_fields_new_complex_tbl(VALUE klass, st_table *tbl);
|
VALUE rb_imemo_fields_new_complex_tbl(VALUE owner, st_table *tbl);
|
||||||
VALUE rb_imemo_fields_clone(VALUE fields_obj);
|
VALUE rb_imemo_fields_clone(VALUE fields_obj);
|
||||||
void rb_imemo_fields_clear(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 *
|
static inline VALUE *
|
||||||
rb_imemo_fields_ptr(VALUE obj_fields)
|
rb_imemo_fields_ptr(VALUE obj_fields)
|
||||||
{
|
{
|
||||||
|
|
|
@ -277,20 +277,20 @@ module Prism
|
||||||
when :tCOMMENT
|
when :tCOMMENT
|
||||||
if token.type == :EMBDOC_BEGIN
|
if token.type == :EMBDOC_BEGIN
|
||||||
|
|
||||||
while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1)
|
while !((next_token = lexed[index]&.first) && next_token.type == :EMBDOC_END) && (index < length - 1)
|
||||||
value += next_token.value
|
value += next_token.value
|
||||||
index += 1
|
index += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
value += next_token.value
|
value += next_token.value
|
||||||
location = range(token.location.start_offset, lexed[index][0].location.end_offset)
|
location = range(token.location.start_offset, next_token.location.end_offset)
|
||||||
index += 1
|
index += 1
|
||||||
else
|
else
|
||||||
is_at_eol = value.chomp!.nil?
|
is_at_eol = value.chomp!.nil?
|
||||||
location = range(token.location.start_offset, token.location.end_offset + (is_at_eol ? 0 : -1))
|
location = range(token.location.start_offset, token.location.end_offset + (is_at_eol ? 0 : -1))
|
||||||
|
|
||||||
prev_token = lexed[index - 2][0] if index - 2 >= 0
|
prev_token, _ = lexed[index - 2] if index - 2 >= 0
|
||||||
next_token = lexed[index][0]
|
next_token, _ = lexed[index]
|
||||||
|
|
||||||
is_inline_comment = prev_token&.location&.start_line == token.location.start_line
|
is_inline_comment = prev_token&.location&.start_line == token.location.start_line
|
||||||
if is_inline_comment && !is_at_eol && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type)
|
if is_inline_comment && !is_at_eol && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type)
|
||||||
|
@ -309,7 +309,7 @@ module Prism
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
when :tNL
|
when :tNL
|
||||||
next_token = next_token = lexed[index][0]
|
next_token, _ = lexed[index]
|
||||||
# Newlines after comments are emitted out of order.
|
# Newlines after comments are emitted out of order.
|
||||||
if next_token&.type == :COMMENT
|
if next_token&.type == :COMMENT
|
||||||
comment_newline_location = location
|
comment_newline_location = location
|
||||||
|
@ -346,8 +346,8 @@ module Prism
|
||||||
location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value))
|
location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value))
|
||||||
value = nil
|
value = nil
|
||||||
when :tSTRING_BEG
|
when :tSTRING_BEG
|
||||||
next_token = lexed[index][0]
|
next_token, _ = lexed[index]
|
||||||
next_next_token = lexed[index + 1][0]
|
next_next_token, _ = lexed[index + 1]
|
||||||
basic_quotes = value == '"' || value == "'"
|
basic_quotes = value == '"' || value == "'"
|
||||||
|
|
||||||
if basic_quotes && next_token&.type == :STRING_END
|
if basic_quotes && next_token&.type == :STRING_END
|
||||||
|
@ -415,7 +415,8 @@ module Prism
|
||||||
while token.type == :STRING_CONTENT
|
while token.type == :STRING_CONTENT
|
||||||
current_length += token.value.bytesize
|
current_length += token.value.bytesize
|
||||||
# Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line.
|
# Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line.
|
||||||
is_first_token_on_line = lexed[index - 1] && token.location.start_line != lexed[index - 2][0].location&.start_line
|
prev_token, _ = lexed[index - 2] if index - 2 >= 0
|
||||||
|
is_first_token_on_line = prev_token && token.location.start_line != prev_token.location.start_line
|
||||||
# The parser gem only removes indentation when the heredoc is not nested
|
# The parser gem only removes indentation when the heredoc is not nested
|
||||||
not_nested = heredoc_stack.size == 1
|
not_nested = heredoc_stack.size == 1
|
||||||
if is_percent_array
|
if is_percent_array
|
||||||
|
@ -434,7 +435,7 @@ module Prism
|
||||||
tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]]
|
tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]]
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
token = lexed[index][0]
|
token, _ = lexed[index]
|
||||||
index += 1
|
index += 1
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -489,7 +490,7 @@ module Prism
|
||||||
end
|
end
|
||||||
|
|
||||||
if percent_array?(quote_stack.pop)
|
if percent_array?(quote_stack.pop)
|
||||||
prev_token = lexed[index - 2][0] if index - 2 >= 0
|
prev_token, _ = lexed[index - 2] if index - 2 >= 0
|
||||||
empty = %i[PERCENT_LOWER_I PERCENT_LOWER_W PERCENT_UPPER_I PERCENT_UPPER_W].include?(prev_token&.type)
|
empty = %i[PERCENT_LOWER_I PERCENT_LOWER_W PERCENT_UPPER_I PERCENT_UPPER_W].include?(prev_token&.type)
|
||||||
ends_with_whitespace = prev_token&.type == :WORDS_SEP
|
ends_with_whitespace = prev_token&.type == :WORDS_SEP
|
||||||
# parser always emits a space token after content in a percent array, even if no actual whitespace is present.
|
# parser always emits a space token after content in a percent array, even if no actual whitespace is present.
|
||||||
|
@ -498,7 +499,7 @@ module Prism
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
when :tSYMBEG
|
when :tSYMBEG
|
||||||
if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END
|
if (next_token = lexed[index]&.first) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END
|
||||||
next_location = token.location.join(next_token.location)
|
next_location = token.location.join(next_token.location)
|
||||||
type = :tSYMBOL
|
type = :tSYMBOL
|
||||||
value = next_token.value
|
value = next_token.value
|
||||||
|
@ -513,13 +514,13 @@ module Prism
|
||||||
type = :tIDENTIFIER
|
type = :tIDENTIFIER
|
||||||
end
|
end
|
||||||
when :tXSTRING_BEG
|
when :tXSTRING_BEG
|
||||||
if (next_token = lexed[index][0]) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type)
|
if (next_token = lexed[index]&.first) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type)
|
||||||
# self.`()
|
# self.`()
|
||||||
type = :tBACK_REF2
|
type = :tBACK_REF2
|
||||||
end
|
end
|
||||||
quote_stack.push(value)
|
quote_stack.push(value)
|
||||||
when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG
|
when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG
|
||||||
if (next_token = lexed[index][0]) && next_token.type == :WORDS_SEP
|
if (next_token = lexed[index]&.first) && next_token.type == :WORDS_SEP
|
||||||
index += 1
|
index += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -595,9 +596,9 @@ module Prism
|
||||||
previous_line = -1
|
previous_line = -1
|
||||||
result = Float::MAX
|
result = Float::MAX
|
||||||
|
|
||||||
while (lexed[next_token_index] && next_token = lexed[next_token_index][0])
|
while (next_token = lexed[next_token_index]&.first)
|
||||||
next_token_index += 1
|
next_token_index += 1
|
||||||
next_next_token = lexed[next_token_index] && lexed[next_token_index][0]
|
next_next_token, _ = lexed[next_token_index]
|
||||||
first_token_on_line = next_token.location.start_column == 0
|
first_token_on_line = next_token.location.start_column == 0
|
||||||
|
|
||||||
# String content inside nested heredocs and interpolation is ignored
|
# String content inside nested heredocs and interpolation is ignored
|
||||||
|
|
|
@ -4,6 +4,7 @@ require 'socket'
|
||||||
require 'timeout'
|
require 'timeout'
|
||||||
require 'io/wait'
|
require 'io/wait'
|
||||||
require 'securerandom'
|
require 'securerandom'
|
||||||
|
require 'rbconfig'
|
||||||
|
|
||||||
# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
|
# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
|
||||||
# handle multiple DNS requests concurrently without blocking the entire Ruby
|
# handle multiple DNS requests concurrently without blocking the entire Ruby
|
||||||
|
|
9
ruby.c
9
ruby.c
|
@ -61,7 +61,6 @@
|
||||||
#include "ruby/util.h"
|
#include "ruby/util.h"
|
||||||
#include "ruby/version.h"
|
#include "ruby/version.h"
|
||||||
#include "ruby/internal/error.h"
|
#include "ruby/internal/error.h"
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
#define singlebit_only_p(x) !((x) & ((x)-1))
|
#define singlebit_only_p(x) !((x) & ((x)-1))
|
||||||
STATIC_ASSERT(Qnil_1bit_from_Qfalse, singlebit_only_p(Qnil^Qfalse));
|
STATIC_ASSERT(Qnil_1bit_from_Qfalse, singlebit_only_p(Qnil^Qfalse));
|
||||||
|
@ -302,6 +301,8 @@ ruby_show_usage_line(const char *name, const char *secondary, const char *descri
|
||||||
description, help, highlight, width, columns);
|
description, help, highlight, width, columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RUBY_EXTERN const char ruby_api_version_name[];
|
||||||
|
|
||||||
static void
|
static void
|
||||||
usage(const char *name, int help, int highlight, int columns)
|
usage(const char *name, int help, int highlight, int columns)
|
||||||
{
|
{
|
||||||
|
@ -404,9 +405,9 @@ usage(const char *name, int help, int highlight, int columns)
|
||||||
unsigned int w = (columns > 80 ? (columns - 79) / 2 : 0) + 16;
|
unsigned int w = (columns > 80 ? (columns - 79) / 2 : 0) + 16;
|
||||||
#define SHOW(m) show_usage_line(&(m), help, highlight, w, columns)
|
#define SHOW(m) show_usage_line(&(m), help, highlight, w, columns)
|
||||||
|
|
||||||
printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n\n", sb, se, name);
|
printf("%sUsage:%s %s [options] [--] [filepath] [arguments]\n", sb, se, name);
|
||||||
printf("Details and examples at https://docs.ruby-lang.org/en/%s/ruby/options_md.html\n",
|
printf("\n""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));
|
ruby_api_version_name);
|
||||||
|
|
||||||
for (i = 0; i < num; ++i)
|
for (i = 0; i < num; ++i)
|
||||||
SHOW(usage_msg[i]);
|
SHOW(usage_msg[i]);
|
||||||
|
|
13
shape.c
13
shape.c
|
@ -877,8 +877,17 @@ shape_get_next(rb_shape_t *shape, VALUE obj, ID id, bool emit_warnings)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
VALUE klass;
|
VALUE klass;
|
||||||
if (IMEMO_TYPE_P(obj, imemo_fields)) { // HACK
|
if (IMEMO_TYPE_P(obj, imemo_fields)) {
|
||||||
klass = CLASS_OF(obj);
|
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 {
|
else {
|
||||||
klass = rb_obj_class(obj);
|
klass = rb_obj_class(obj);
|
||||||
|
|
|
@ -163,6 +163,22 @@ module Prism
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_invalid_syntax
|
||||||
|
code = <<~RUBY
|
||||||
|
foo do
|
||||||
|
case bar
|
||||||
|
when
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
buffer = Parser::Source::Buffer.new("(string)")
|
||||||
|
buffer.source = code
|
||||||
|
|
||||||
|
parser = Prism::Translation::Parser33.new
|
||||||
|
parser.diagnostics.all_errors_are_fatal = true
|
||||||
|
assert_raise(Parser::SyntaxError) { parser.tokenize(buffer) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_it_block_parameter_syntax
|
def test_it_block_parameter_syntax
|
||||||
it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/it.txt")
|
it_fixture_path = Pathname(__dir__).join("../../../test/prism/fixtures/it.txt")
|
||||||
|
|
||||||
|
|
|
@ -47,26 +47,27 @@ class TestRubyOptions < Test::Unit::TestCase
|
||||||
assert_in_out_err([], "", [], [])
|
assert_in_out_err([], "", [], [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
version = RUBY_PATCHLEVEL == -1 ? "master" : "#{RUBY_VERSION_MAJOR}.#{RUBY_VERSION_MINOR}"
|
||||||
|
OPTIONS_LINK = "https://docs.ruby-lang.org/en/#{version}/ruby/options_md.html"
|
||||||
|
|
||||||
def test_usage
|
def test_usage
|
||||||
assert_in_out_err(%w(-h)) do |r, e|
|
assert_in_out_err(%w(-h)) do |r, e|
|
||||||
assert_operator(r.size, :<=, 26)
|
_, _, link, *r = r
|
||||||
longer = r[3..-1].select {|x| x.size >= 80}
|
assert_include(link, OPTIONS_LINK)
|
||||||
|
assert_operator(r.size, :<=, 24)
|
||||||
|
longer = r.select {|x| x.size >= 80}
|
||||||
assert_equal([], longer)
|
assert_equal([], longer)
|
||||||
assert_equal([], e)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_usage_long
|
def test_usage_long
|
||||||
assert_in_out_err(%w(--help)) do |r, e|
|
assert_in_out_err(%w(--help)) do |r, e|
|
||||||
longer = r[3..-1].select {|x| x.size > 80}
|
_, _, link, *r = r
|
||||||
|
assert_include(link, OPTIONS_LINK)
|
||||||
|
longer = r.select {|x| x.size > 80}
|
||||||
assert_equal([], longer)
|
assert_equal([], longer)
|
||||||
assert_equal([], e)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -149,11 +149,14 @@ class TestShapes < Test::Unit::TestCase
|
||||||
def test_too_many_ivs_on_class
|
def test_too_many_ivs_on_class
|
||||||
obj = Class.new
|
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)
|
obj.instance_variable_set(:"@a#{_1}", 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_false RubyVM::Shape.of(obj).too_complex?
|
refute_predicate RubyVM::Shape.of(obj), :too_complex?
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_removing_when_too_many_ivs_on_class
|
def test_removing_when_too_many_ivs_on_class
|
||||||
|
|
|
@ -80,6 +80,16 @@ class TestZJIT < Test::Unit::TestCase
|
||||||
}, insns: [:setglobal]
|
}, insns: [:setglobal]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_string_intern
|
||||||
|
assert_compiles ':foo123', %q{
|
||||||
|
def test
|
||||||
|
:"foo#{123}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test
|
||||||
|
}, insns: [:intern]
|
||||||
|
end
|
||||||
|
|
||||||
def test_setglobal_with_trace_var_exception
|
def test_setglobal_with_trace_var_exception
|
||||||
assert_compiles '"rescued"', %q{
|
assert_compiles '"rescued"', %q{
|
||||||
def test
|
def test
|
||||||
|
@ -1351,6 +1361,71 @@ class TestZJIT < Test::Unit::TestCase
|
||||||
}, call_threshold: 2
|
}, call_threshold: 2
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_objtostring_calls_to_s_on_non_strings
|
||||||
|
assert_compiles '["foo", "foo"]', %q{
|
||||||
|
results = []
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
def to_s
|
||||||
|
"foo"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test(str)
|
||||||
|
"#{str}"
|
||||||
|
end
|
||||||
|
|
||||||
|
results << test(Foo.new)
|
||||||
|
results << test(Foo.new)
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_objtostring_rewrite_does_not_call_to_s_on_strings
|
||||||
|
assert_compiles '["foo", "foo"]', %q{
|
||||||
|
results = []
|
||||||
|
|
||||||
|
class String
|
||||||
|
def to_s
|
||||||
|
"bad"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test(foo)
|
||||||
|
"#{foo}"
|
||||||
|
end
|
||||||
|
|
||||||
|
results << test("foo")
|
||||||
|
results << test("foo")
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_objtostring_rewrite_does_not_call_to_s_on_string_subclasses
|
||||||
|
assert_compiles '["foo", "foo"]', %q{
|
||||||
|
results = []
|
||||||
|
|
||||||
|
class StringSubclass < String
|
||||||
|
def to_s
|
||||||
|
"bad"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
foo = StringSubclass.new("foo")
|
||||||
|
|
||||||
|
def test(str)
|
||||||
|
"#{str}"
|
||||||
|
end
|
||||||
|
|
||||||
|
results << test(foo)
|
||||||
|
results << test(foo)
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def test_string_bytesize_with_guard
|
def test_string_bytesize_with_guard
|
||||||
assert_compiles '5', %q{
|
assert_compiles '5', %q{
|
||||||
def test(str)
|
def test(str)
|
||||||
|
|
|
@ -8,11 +8,15 @@ ENV.delete("GNUMAKEFLAGS")
|
||||||
|
|
||||||
github_actions = ENV["GITHUB_ACTIONS"] == "true"
|
github_actions = ENV["GITHUB_ACTIONS"] == "true"
|
||||||
|
|
||||||
|
DEFAULT_ALLOWED_FAILURES = RUBY_PLATFORM =~ /mswin|mingw/ ? [
|
||||||
|
'rbs',
|
||||||
|
'debug',
|
||||||
|
'irb',
|
||||||
|
'power_assert',
|
||||||
|
'net-imap',
|
||||||
|
] : []
|
||||||
allowed_failures = ENV['TEST_BUNDLED_GEMS_ALLOW_FAILURES'] || ''
|
allowed_failures = ENV['TEST_BUNDLED_GEMS_ALLOW_FAILURES'] || ''
|
||||||
if RUBY_PLATFORM =~ /mswin|mingw/
|
allowed_failures = allowed_failures.split(',').concat(DEFAULT_ALLOWED_FAILURES).uniq.reject(&:empty?)
|
||||||
allowed_failures = [allowed_failures, "rbs,debug,irb,power_assert"].join(',')
|
|
||||||
end
|
|
||||||
allowed_failures = allowed_failures.split(',').uniq.reject(&:empty?)
|
|
||||||
|
|
||||||
# make test-bundled-gems BUNDLED_GEMS=gem1,gem2,gem3
|
# make test-bundled-gems BUNDLED_GEMS=gem1,gem2,gem3
|
||||||
bundled_gems = ARGV.first || ''
|
bundled_gems = ARGV.first || ''
|
||||||
|
@ -113,7 +117,7 @@ File.foreach("#{gem_dir}/bundled_gems") do |line|
|
||||||
"with exit code #{$?.exitstatus}")
|
"with exit code #{$?.exitstatus}")
|
||||||
puts colorize.decorate(mesg, "fail")
|
puts colorize.decorate(mesg, "fail")
|
||||||
if allowed_failures.include?(gem)
|
if allowed_failures.include?(gem)
|
||||||
mesg = "Ignoring test failures for #{gem} due to \$TEST_BUNDLED_GEMS_ALLOW_FAILURES"
|
mesg = "Ignoring test failures for #{gem} due to \$TEST_BUNDLED_GEMS_ALLOW_FAILURES or DEFAULT_ALLOWED_FAILURES"
|
||||||
puts colorize.decorate(mesg, "skip")
|
puts colorize.decorate(mesg, "skip")
|
||||||
else
|
else
|
||||||
failed << gem
|
failed << gem
|
||||||
|
|
65
variable.c
65
variable.c
|
@ -1245,9 +1245,19 @@ rb_obj_fields(VALUE obj, ID field_name)
|
||||||
goto generic_fields;
|
goto generic_fields;
|
||||||
default:
|
default:
|
||||||
generic_fields:
|
generic_fields:
|
||||||
RB_VM_LOCKING() {
|
{
|
||||||
if (!st_lookup(generic_fields_tbl_, (st_data_t)obj, (st_data_t *)&fields_obj)) {
|
rb_execution_context_t *ec = GET_EC();
|
||||||
rb_bug("Object is missing entry in generic_fields_tbl");
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1275,8 +1285,15 @@ rb_free_generic_ivar(VALUE obj)
|
||||||
goto generic_fields;
|
goto generic_fields;
|
||||||
default:
|
default:
|
||||||
generic_fields:
|
generic_fields:
|
||||||
RB_VM_LOCKING() {
|
{
|
||||||
st_delete(generic_fields_tbl_no_ractor_check(), &key, &value);
|
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);
|
RBASIC_SET_SHAPE_ID(obj, ROOT_SHAPE_ID);
|
||||||
|
@ -1307,10 +1324,18 @@ rb_obj_set_fields(VALUE obj, VALUE fields_obj, ID field_name, VALUE original_fie
|
||||||
goto generic_fields;
|
goto generic_fields;
|
||||||
default:
|
default:
|
||||||
generic_fields:
|
generic_fields:
|
||||||
RB_VM_LOCKING() {
|
{
|
||||||
st_insert(generic_fields_tbl_, (st_data_t)obj, (st_data_t)fields_obj);
|
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) {
|
if (original_fields_obj) {
|
||||||
|
@ -1687,10 +1712,10 @@ imemo_fields_complex_from_obj_i(ID key, VALUE val, st_data_t arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
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;
|
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);
|
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);
|
RBASIC_SET_SHAPE_ID(fields_obj, shape_id);
|
||||||
|
@ -1699,9 +1724,9 @@ imemo_fields_complex_from_obj(VALUE klass, VALUE source_fields_obj, shape_id_t s
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
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) {
|
if (source_fields_obj) {
|
||||||
attr_index_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj));
|
attr_index_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(source_fields_obj));
|
||||||
VALUE *fields = rb_imemo_fields_ptr(fields_obj);
|
VALUE *fields = rb_imemo_fields_ptr(fields_obj);
|
||||||
|
@ -1853,7 +1878,7 @@ general_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val, void *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
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;
|
const VALUE original_fields_obj = fields_obj;
|
||||||
shape_id_t current_shape_id = fields_obj ? RBASIC_SHAPE_ID(fields_obj) : ROOT_SHAPE_ID;
|
shape_id_t current_shape_id = fields_obj ? RBASIC_SHAPE_ID(fields_obj) : ROOT_SHAPE_ID;
|
||||||
|
@ -1868,7 +1893,7 @@ imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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;
|
current_shape_id = target_shape_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1882,7 +1907,7 @@ imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID f
|
||||||
else {
|
else {
|
||||||
attr_index_t index = RSHAPE_INDEX(target_shape_id);
|
attr_index_t index = RSHAPE_INDEX(target_shape_id);
|
||||||
if (concurrent || index >= RSHAPE_CAPACITY(current_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);
|
VALUE *table = rb_imemo_fields_ptr(fields_obj);
|
||||||
|
@ -1905,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);
|
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);
|
rb_obj_set_fields(obj, fields_obj, field_name, original_fields_obj);
|
||||||
}
|
}
|
||||||
|
@ -2340,7 +2365,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj)
|
||||||
return;
|
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 *src_buf = rb_imemo_fields_ptr(fields_obj);
|
||||||
VALUE *dest_buf = rb_imemo_fields_ptr(new_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);
|
rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, src_buf, src_shape_id);
|
||||||
|
@ -4640,7 +4665,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc
|
||||||
{
|
{
|
||||||
bool existing = true;
|
bool existing = true;
|
||||||
const VALUE original_fields_obj = fields_obj;
|
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 current_shape_id = RBASIC_SHAPE_ID(fields_obj);
|
||||||
shape_id_t next_shape_id = current_shape_id;
|
shape_id_t next_shape_id = current_shape_id;
|
||||||
|
@ -4660,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);
|
next_shape_id = rb_shape_transition_add_ivar(fields_obj, id);
|
||||||
if (UNLIKELY(rb_shape_too_complex_p(next_shape_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;
|
goto too_complex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4670,7 +4695,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc
|
||||||
if (next_capacity > current_capacity) {
|
if (next_capacity > current_capacity) {
|
||||||
// We allocate a new fields_obj even when concurrency isn't a concern
|
// We allocate a new fields_obj even when concurrency isn't a concern
|
||||||
// so that we're embedded as long as possible.
|
// 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);
|
RUBY_ASSERT(RSHAPE(next_shape_id)->type == SHAPE_IVAR);
|
||||||
|
|
|
@ -24,11 +24,13 @@
|
||||||
|
|
||||||
#ifdef RUBY_REVISION
|
#ifdef RUBY_REVISION
|
||||||
# if RUBY_PATCHLEVEL == -1
|
# if RUBY_PATCHLEVEL == -1
|
||||||
|
# define RUBY_API_VERSION_NAME "master"
|
||||||
# ifndef RUBY_BRANCH_NAME
|
# ifndef RUBY_BRANCH_NAME
|
||||||
# define RUBY_BRANCH_NAME "master"
|
# define RUBY_BRANCH_NAME RUBY_API_VERSION_NAME
|
||||||
# endif
|
# endif
|
||||||
# define RUBY_REVISION_STR " "RUBY_BRANCH_NAME" "RUBY_REVISION
|
# define RUBY_REVISION_STR " "RUBY_BRANCH_NAME" "RUBY_REVISION
|
||||||
# else
|
# else
|
||||||
|
# define RUBY_API_VERSION_NAME RUBY_API_VERSION_STR
|
||||||
# define RUBY_REVISION_STR " revision "RUBY_REVISION
|
# define RUBY_REVISION_STR " revision "RUBY_REVISION
|
||||||
# endif
|
# endif
|
||||||
#else
|
#else
|
||||||
|
@ -44,6 +46,10 @@
|
||||||
#define MKSTR(type) rb_obj_freeze(rb_usascii_str_new_static(ruby_##type, sizeof(ruby_##type)-1))
|
#define MKSTR(type) rb_obj_freeze(rb_usascii_str_new_static(ruby_##type, sizeof(ruby_##type)-1))
|
||||||
#define MKINT(name) INT2FIX(ruby_##name)
|
#define MKINT(name) INT2FIX(ruby_##name)
|
||||||
|
|
||||||
|
#define RUBY_API_VERSION_STR \
|
||||||
|
STRINGIZE(RUBY_API_VERSION_MAJOR) "." \
|
||||||
|
STRINGIZE(RUBY_API_VERSION_MINOR) "." \
|
||||||
|
""
|
||||||
const int ruby_api_version[] = {
|
const int ruby_api_version[] = {
|
||||||
RUBY_API_VERSION_MAJOR,
|
RUBY_API_VERSION_MAJOR,
|
||||||
RUBY_API_VERSION_MINOR,
|
RUBY_API_VERSION_MINOR,
|
||||||
|
@ -76,6 +82,7 @@ const char ruby_revision[] = RUBY_FULL_REVISION;
|
||||||
const char ruby_release_date[] = RUBY_RELEASE_DATE;
|
const char ruby_release_date[] = RUBY_RELEASE_DATE;
|
||||||
const char ruby_platform[] = RUBY_PLATFORM;
|
const char ruby_platform[] = RUBY_PLATFORM;
|
||||||
const int ruby_patchlevel = RUBY_PATCHLEVEL;
|
const int ruby_patchlevel = RUBY_PATCHLEVEL;
|
||||||
|
const char ruby_api_version_name[] = RUBY_API_VERSION_NAME;
|
||||||
const char ruby_description[] =
|
const char ruby_description[] =
|
||||||
"ruby " RUBY_VERSION RUBY_PATCHLEVEL_STR " "
|
"ruby " RUBY_VERSION RUBY_PATCHLEVEL_STR " "
|
||||||
"(" RUBY_RELEASE_DATETIME RUBY_REVISION_STR ") "
|
"(" RUBY_RELEASE_DATETIME RUBY_REVISION_STR ") "
|
||||||
|
|
6
vm.c
6
vm.c
|
@ -3441,6 +3441,9 @@ rb_execution_context_update(rb_execution_context_t *ec)
|
||||||
}
|
}
|
||||||
|
|
||||||
ec->storage = rb_gc_location(ec->storage);
|
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
|
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(ec->private_const_reference);
|
||||||
|
|
||||||
rb_gc_mark_movable(ec->storage);
|
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);
|
void rb_fiber_mark_self(rb_fiber_t *fib);
|
||||||
|
|
|
@ -1070,6 +1070,11 @@ struct rb_execution_context_struct {
|
||||||
|
|
||||||
VALUE private_const_reference;
|
VALUE private_const_reference;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
VALUE obj;
|
||||||
|
VALUE fields_obj;
|
||||||
|
} gen_fields_cache;
|
||||||
|
|
||||||
/* for GC */
|
/* for GC */
|
||||||
struct {
|
struct {
|
||||||
VALUE *stack_start;
|
VALUE *stack_start;
|
||||||
|
|
|
@ -6209,6 +6209,14 @@ vm_objtostring(const rb_iseq_t *iseq, VALUE recv, CALL_DATA cd)
|
||||||
return Qundef;
|
return Qundef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ZJIT implementation is using the C function
|
||||||
|
// and needs to call a non-static function
|
||||||
|
VALUE
|
||||||
|
rb_vm_objtostring(const rb_iseq_t *iseq, VALUE recv, CALL_DATA cd)
|
||||||
|
{
|
||||||
|
return vm_objtostring(iseq, recv, cd);
|
||||||
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
vm_opt_ary_freeze(VALUE ary, int bop, ID id)
|
vm_opt_ary_freeze(VALUE ary, int bop, ID id)
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,7 +47,6 @@ impl From<BranchCond> for [u8; 4] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -77,4 +76,3 @@ mod tests {
|
||||||
assert_eq!(0x54800000, result);
|
assert_eq!(0x54800000, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -60,7 +60,6 @@ impl From<Conditional> for [u8; 4] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -72,4 +71,3 @@ mod tests {
|
||||||
assert_eq!(0x9a821020, result);
|
assert_eq!(0x9a821020, result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -345,7 +345,6 @@ pub fn uimm_num_bits(uimm: u64) -> u8
|
||||||
return 64;
|
return 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests
|
mod tests
|
||||||
{
|
{
|
||||||
|
@ -381,32 +380,5 @@ mod tests
|
||||||
assert_eq!(uimm_num_bits((u32::MAX as u64) + 1), 64);
|
assert_eq!(uimm_num_bits((u32::MAX as u64) + 1), 64);
|
||||||
assert_eq!(uimm_num_bits(u64::MAX), 64);
|
assert_eq!(uimm_num_bits(u64::MAX), 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_code_size() {
|
|
||||||
// Write 4 bytes in the first page
|
|
||||||
let mut cb = CodeBlock::new_dummy(CodeBlock::PREFERRED_CODE_PAGE_SIZE * 2);
|
|
||||||
cb.write_bytes(&[0, 0, 0, 0]);
|
|
||||||
assert_eq!(cb.code_size(), 4);
|
|
||||||
|
|
||||||
// Moving to the next page should not increase code_size
|
|
||||||
cb.next_page(cb.get_write_ptr(), |_, _| {});
|
|
||||||
assert_eq!(cb.code_size(), 4);
|
|
||||||
|
|
||||||
// Write 4 bytes in the second page
|
|
||||||
cb.write_bytes(&[0, 0, 0, 0]);
|
|
||||||
assert_eq!(cb.code_size(), 8);
|
|
||||||
|
|
||||||
// Rewrite 4 bytes in the first page
|
|
||||||
let old_write_pos = cb.get_write_pos();
|
|
||||||
cb.set_pos(0);
|
|
||||||
cb.write_bytes(&[1, 1, 1, 1]);
|
|
||||||
|
|
||||||
// Moving from an old page to the next page should not increase code_size
|
|
||||||
cb.next_page(cb.get_write_ptr(), |_, _| {});
|
|
||||||
cb.set_pos(old_write_pos);
|
|
||||||
assert_eq!(cb.code_size(), 8);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
|
@ -317,34 +317,6 @@ pub fn mem_opnd_sib(num_bits: u8, base_opnd: X86Opnd, index_opnd: X86Opnd, scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// Struct member operand
|
|
||||||
#define member_opnd(base_reg, struct_type, member_name) mem_opnd( \
|
|
||||||
8 * sizeof(((struct_type*)0)->member_name), \
|
|
||||||
base_reg, \
|
|
||||||
offsetof(struct_type, member_name) \
|
|
||||||
)
|
|
||||||
|
|
||||||
// Struct member operand with an array index
|
|
||||||
#define member_opnd_idx(base_reg, struct_type, member_name, idx) mem_opnd( \
|
|
||||||
8 * sizeof(((struct_type*)0)->member_name[0]), \
|
|
||||||
base_reg, \
|
|
||||||
(offsetof(struct_type, member_name) + \
|
|
||||||
sizeof(((struct_type*)0)->member_name[0]) * idx) \
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
// TODO: this should be a method, X86Opnd.resize() or X86Opnd.subreg()
|
|
||||||
static x86opnd_t resize_opnd(x86opnd_t opnd, uint32_t num_bits)
|
|
||||||
{
|
|
||||||
assert (num_bits % 8 == 0);
|
|
||||||
x86opnd_t sub = opnd;
|
|
||||||
sub.num_bits = num_bits;
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub fn imm_opnd(value: i64) -> X86Opnd
|
pub fn imm_opnd(value: i64) -> X86Opnd
|
||||||
{
|
{
|
||||||
X86Opnd::Imm(X86Imm { num_bits: imm_num_bits(value), value })
|
X86Opnd::Imm(X86Imm { num_bits: imm_num_bits(value), value })
|
||||||
|
@ -1103,46 +1075,6 @@ pub fn movsx(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// movzx - Move with zero extension (unsigned values)
|
|
||||||
void movzx(codeblock_t *cb, x86opnd_t dst, x86opnd_t src)
|
|
||||||
{
|
|
||||||
cb.writeASM("movzx", dst, src);
|
|
||||||
|
|
||||||
uint32_t dstSize;
|
|
||||||
if (dst.isReg)
|
|
||||||
dstSize = dst.reg.size;
|
|
||||||
else
|
|
||||||
assert (false, "movzx dst must be a register");
|
|
||||||
|
|
||||||
uint32_t srcSize;
|
|
||||||
if (src.isReg)
|
|
||||||
srcSize = src.reg.size;
|
|
||||||
else if (src.isMem)
|
|
||||||
srcSize = src.mem.size;
|
|
||||||
else
|
|
||||||
assert (false);
|
|
||||||
|
|
||||||
assert (
|
|
||||||
srcSize < dstSize,
|
|
||||||
"movzx: srcSize >= dstSize"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (srcSize is 8)
|
|
||||||
{
|
|
||||||
cb.writeRMInstr!('r', 0xFF, 0x0F, 0xB6)(dstSize is 16, dstSize is 64, dst, src);
|
|
||||||
}
|
|
||||||
else if (srcSize is 16)
|
|
||||||
{
|
|
||||||
cb.writeRMInstr!('r', 0xFF, 0x0F, 0xB7)(dstSize is 16, dstSize is 64, dst, src);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert (false, "invalid src operand size for movxz");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// nop - Noop, one or multiple bytes long
|
/// nop - Noop, one or multiple bytes long
|
||||||
pub fn nop(cb: &mut CodeBlock, length: u32) {
|
pub fn nop(cb: &mut CodeBlock, length: u32) {
|
||||||
match length {
|
match length {
|
||||||
|
|
|
@ -317,7 +317,7 @@ impl Assembler
|
||||||
asm.load(opnd)
|
asm.load(opnd)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Opnd::None | Opnd::Value(_) /*| Opnd::Stack { .. }*/ => unreachable!()
|
Opnd::None | Opnd::Value(_) => unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1372,7 +1372,7 @@ impl Assembler
|
||||||
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Option<(CodePtr, Vec<CodePtr>)> {
|
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Option<(CodePtr, Vec<CodePtr>)> {
|
||||||
let asm = self.arm64_split();
|
let asm = self.arm64_split();
|
||||||
let mut asm = asm.alloc_regs(regs)?;
|
let mut asm = asm.alloc_regs(regs)?;
|
||||||
asm.compile_side_exits()?;
|
asm.compile_side_exits();
|
||||||
|
|
||||||
// Create label instances in the code block
|
// Create label instances in the code block
|
||||||
for (idx, name) in asm.label_names.iter().enumerate() {
|
for (idx, name) in asm.label_names.iter().enumerate() {
|
||||||
|
@ -1742,13 +1742,12 @@ mod tests {
|
||||||
asm.compile_with_num_regs(&mut cb, 0);
|
asm.compile_with_num_regs(&mut cb, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_emit_lea_label() {
|
fn test_emit_lea_label() {
|
||||||
let (mut asm, mut cb) = setup_asm();
|
let (mut asm, mut cb) = setup_asm();
|
||||||
|
|
||||||
let label = asm.new_label("label");
|
let label = asm.new_label("label");
|
||||||
let opnd = asm.lea_jump_target(label);
|
let opnd = asm.lea_jump_target(label.clone());
|
||||||
|
|
||||||
asm.write_label(label);
|
asm.write_label(label);
|
||||||
asm.bake_string("Hello, world!");
|
asm.bake_string("Hello, world!");
|
||||||
|
@ -1756,7 +1755,6 @@ mod tests {
|
||||||
|
|
||||||
asm.compile_with_num_regs(&mut cb, 1);
|
asm.compile_with_num_regs(&mut cb, 1);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_emit_load_mem_disp_fits_into_load() {
|
fn test_emit_load_mem_disp_fits_into_load() {
|
||||||
|
@ -1967,48 +1965,6 @@ mod tests {
|
||||||
asm.compile_with_num_regs(&mut cb, 2);
|
asm.compile_with_num_regs(&mut cb, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
#[test]
|
|
||||||
fn test_bcond_straddling_code_pages() {
|
|
||||||
const LANDING_PAGE: usize = 65;
|
|
||||||
let mut asm = Assembler::new(0);
|
|
||||||
let mut cb = CodeBlock::new_dummy_with_freed_pages(vec![0, LANDING_PAGE]);
|
|
||||||
|
|
||||||
// Skip to near the end of the page. Room for two instructions.
|
|
||||||
cb.set_pos(cb.page_start_pos() + cb.page_end() - 8);
|
|
||||||
|
|
||||||
let end = asm.new_label("end");
|
|
||||||
// Start with a conditional jump...
|
|
||||||
asm.jz(end);
|
|
||||||
|
|
||||||
// A few instructions, enough to cause a page switch.
|
|
||||||
let sum = asm.add(399.into(), 111.into());
|
|
||||||
let xorred = asm.xor(sum, 859.into());
|
|
||||||
asm.store(Opnd::mem(64, Opnd::Reg(X2_REG), 0), xorred);
|
|
||||||
asm.store(Opnd::mem(64, Opnd::Reg(X0_REG), 0), xorred);
|
|
||||||
|
|
||||||
// The branch target. It should be in the landing page.
|
|
||||||
asm.write_label(end);
|
|
||||||
asm.cret(xorred);
|
|
||||||
|
|
||||||
// [Bug #19385]
|
|
||||||
// This used to panic with "The offset must be 19 bits or less."
|
|
||||||
// due to attempting to lower the `asm.jz` above to a `b.e` with an offset that's > 1 MiB.
|
|
||||||
let starting_pos = cb.get_write_pos();
|
|
||||||
asm.compile_with_num_regs(&mut cb, 2);
|
|
||||||
let gap = cb.get_write_pos() - starting_pos;
|
|
||||||
assert!(gap > 0b1111111111111111111);
|
|
||||||
|
|
||||||
let instruction_at_starting_pos: [u8; 4] = unsafe {
|
|
||||||
std::slice::from_raw_parts(cb.get_ptr(starting_pos).raw_ptr(&cb), 4)
|
|
||||||
}.try_into().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
0b000101 << 26_u32,
|
|
||||||
u32::from_le_bytes(instruction_at_starting_pos) & (0b111111 << 26_u32),
|
|
||||||
"starting instruction should be an unconditional branch to the new page (B)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_emit_xor() {
|
fn test_emit_xor() {
|
||||||
let (mut asm, mut cb) = setup_asm();
|
let (mut asm, mut cb) = setup_asm();
|
||||||
|
@ -2018,9 +1974,9 @@ mod tests {
|
||||||
|
|
||||||
asm.compile_with_num_regs(&mut cb, 1);
|
asm.compile_with_num_regs(&mut cb, 1);
|
||||||
|
|
||||||
assert_disasm!(cb, "0b0001ca4b0000f8", "
|
assert_disasm!(cb, "000001ca400000f8", "
|
||||||
0x0: eor x11, x0, x1
|
0x0: eor x0, x0, x1
|
||||||
0x4: stur x11, [x2]
|
0x4: stur x0, [x2]
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2082,10 +2038,10 @@ mod tests {
|
||||||
asm.mov(Opnd::Reg(TEMP_REGS[0]), out);
|
asm.mov(Opnd::Reg(TEMP_REGS[0]), out);
|
||||||
asm.compile_with_num_regs(&mut cb, 2);
|
asm.compile_with_num_regs(&mut cb, 2);
|
||||||
|
|
||||||
assert_disasm!(cb, "8b0280d20c0080d261b18c9a", {"
|
assert_disasm!(cb, "800280d2010080d201b0819a", {"
|
||||||
0x0: mov x11, #0x14
|
0x0: mov x0, #0x14
|
||||||
0x4: mov x12, #0
|
0x4: mov x1, #0
|
||||||
0x8: csel x1, x11, x12, lt
|
0x8: csel x1, x0, x1, lt
|
||||||
"});
|
"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2098,11 +2054,9 @@ mod tests {
|
||||||
asm.mov(Opnd::Reg(TEMP_REGS[0]), out);
|
asm.mov(Opnd::Reg(TEMP_REGS[0]), out);
|
||||||
asm.compile_with_num_regs(&mut cb, 2);
|
asm.compile_with_num_regs(&mut cb, 2);
|
||||||
|
|
||||||
assert_disasm!(cb, "2b0500b16b0500b1e1030baa", {"
|
assert_disasm!(cb, "200500b1010400b1", {"
|
||||||
0x0: adds x11, x9, #1
|
0x0: adds x0, x9, #1
|
||||||
0x4: adds x11, x11, #1
|
0x4: adds x1, x0, #1
|
||||||
0x8: mov x1, x11
|
|
||||||
"});
|
"});
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -218,31 +218,6 @@ impl Opnd
|
||||||
pub fn match_num_bits(opnds: &[Opnd]) -> u8 {
|
pub fn match_num_bits(opnds: &[Opnd]) -> u8 {
|
||||||
Self::match_num_bits_iter(opnds.iter())
|
Self::match_num_bits_iter(opnds.iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Convert Opnd::Stack into RegMapping
|
|
||||||
pub fn reg_opnd(&self) -> RegOpnd {
|
|
||||||
self.get_reg_opnd().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert an operand into RegMapping if it's Opnd::Stack
|
|
||||||
pub fn get_reg_opnd(&self) -> Option<RegOpnd> {
|
|
||||||
match *self {
|
|
||||||
Opnd::Stack { idx, stack_size, num_locals, .. } => Some(
|
|
||||||
if let Some(num_locals) = num_locals {
|
|
||||||
let last_idx = stack_size as i32 + VM_ENV_DATA_SIZE as i32 - 1;
|
|
||||||
assert!(last_idx <= idx, "Local index {} must be >= last local index {}", idx, last_idx);
|
|
||||||
assert!(idx <= last_idx + num_locals as i32, "Local index {} must be < last local index {} + local size {}", idx, last_idx, num_locals);
|
|
||||||
RegOpnd::Local((last_idx + num_locals as i32 - idx) as u8)
|
|
||||||
} else {
|
|
||||||
assert!(idx < stack_size as i32);
|
|
||||||
RegOpnd::Stack((stack_size as i32 - idx - 1) as u8)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<usize> for Opnd {
|
impl From<usize> for Opnd {
|
||||||
|
@ -281,14 +256,6 @@ impl From<VALUE> for Opnd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set of things we need to restore for side exits.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct SideExitContext {
|
|
||||||
pub pc: *const VALUE,
|
|
||||||
pub stack: Vec<Opnd>,
|
|
||||||
pub locals: Vec<Opnd>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Branch target (something that we can jump to)
|
/// Branch target (something that we can jump to)
|
||||||
/// for branch instructions
|
/// for branch instructions
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -300,9 +267,9 @@ pub enum Target
|
||||||
Label(Label),
|
Label(Label),
|
||||||
/// Side exit to the interpreter
|
/// Side exit to the interpreter
|
||||||
SideExit {
|
SideExit {
|
||||||
/// Context to restore on regular side exits. None for side exits right
|
pc: *const VALUE,
|
||||||
/// after JIT-to-JIT calls because we restore them before the JIT call.
|
stack: Vec<Opnd>,
|
||||||
context: Option<SideExitContext>,
|
locals: Vec<Opnd>,
|
||||||
/// We use this to enrich asm comments.
|
/// We use this to enrich asm comments.
|
||||||
reason: SideExitReason,
|
reason: SideExitReason,
|
||||||
/// Some if the side exit should write this label. We use it for patch points.
|
/// Some if the side exit should write this label. We use it for patch points.
|
||||||
|
@ -786,7 +753,7 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
|
||||||
Insn::Label(target) |
|
Insn::Label(target) |
|
||||||
Insn::LeaJumpTarget { target, .. } |
|
Insn::LeaJumpTarget { target, .. } |
|
||||||
Insn::PatchPoint(target) => {
|
Insn::PatchPoint(target) => {
|
||||||
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
|
if let Target::SideExit { stack, locals, .. } = target {
|
||||||
let stack_idx = self.idx;
|
let stack_idx = self.idx;
|
||||||
if stack_idx < stack.len() {
|
if stack_idx < stack.len() {
|
||||||
let opnd = &stack[stack_idx];
|
let opnd = &stack[stack_idx];
|
||||||
|
@ -811,7 +778,7 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
|
||||||
return Some(opnd);
|
return Some(opnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
|
if let Target::SideExit { stack, locals, .. } = target {
|
||||||
let stack_idx = self.idx - 1;
|
let stack_idx = self.idx - 1;
|
||||||
if stack_idx < stack.len() {
|
if stack_idx < stack.len() {
|
||||||
let opnd = &stack[stack_idx];
|
let opnd = &stack[stack_idx];
|
||||||
|
@ -942,7 +909,7 @@ impl<'a> InsnOpndMutIterator<'a> {
|
||||||
Insn::Label(target) |
|
Insn::Label(target) |
|
||||||
Insn::LeaJumpTarget { target, .. } |
|
Insn::LeaJumpTarget { target, .. } |
|
||||||
Insn::PatchPoint(target) => {
|
Insn::PatchPoint(target) => {
|
||||||
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
|
if let Target::SideExit { stack, locals, .. } = target {
|
||||||
let stack_idx = self.idx;
|
let stack_idx = self.idx;
|
||||||
if stack_idx < stack.len() {
|
if stack_idx < stack.len() {
|
||||||
let opnd = &mut stack[stack_idx];
|
let opnd = &mut stack[stack_idx];
|
||||||
|
@ -967,7 +934,7 @@ impl<'a> InsnOpndMutIterator<'a> {
|
||||||
return Some(opnd);
|
return Some(opnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
|
if let Target::SideExit { stack, locals, .. } = target {
|
||||||
let stack_idx = self.idx - 1;
|
let stack_idx = self.idx - 1;
|
||||||
if stack_idx < stack.len() {
|
if stack_idx < stack.len() {
|
||||||
let opnd = &mut stack[stack_idx];
|
let opnd = &mut stack[stack_idx];
|
||||||
|
@ -1213,30 +1180,6 @@ pub struct Assembler {
|
||||||
|
|
||||||
/// Names of labels
|
/// Names of labels
|
||||||
pub(super) label_names: Vec<String>,
|
pub(super) label_names: Vec<String>,
|
||||||
|
|
||||||
/*
|
|
||||||
/// Context for generating the current insn
|
|
||||||
pub ctx: Context,
|
|
||||||
|
|
||||||
/// The current ISEQ's local table size. asm.local_opnd() uses this, and it's
|
|
||||||
/// sometimes hard to pass this value, e.g. asm.spill_regs() in asm.ccall().
|
|
||||||
///
|
|
||||||
/// `None` means we're not assembling for an ISEQ, or that the local size is
|
|
||||||
/// not relevant.
|
|
||||||
pub(super) num_locals: Option<u32>,
|
|
||||||
|
|
||||||
/// Side exit caches for each SideExitContext
|
|
||||||
pub(super) side_exits: HashMap<SideExitContext, CodePtr>,
|
|
||||||
|
|
||||||
/// PC for Target::SideExit
|
|
||||||
side_exit_pc: Option<*mut VALUE>,
|
|
||||||
|
|
||||||
/// Stack size for Target::SideExit
|
|
||||||
side_exit_stack_size: Option<u8>,
|
|
||||||
|
|
||||||
/// If true, the next ccall() should verify its leafness
|
|
||||||
leaf_ccall: bool,
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assembler
|
impl Assembler
|
||||||
|
@ -1246,20 +1189,6 @@ impl Assembler
|
||||||
Self::new_with_label_names(Vec::default(), 0)
|
Self::new_with_label_names(Vec::default(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Create an Assembler for ISEQ-specific code.
|
|
||||||
/// It includes all inline code and some outlined code like side exits and stubs.
|
|
||||||
pub fn new(num_locals: u32) -> Self {
|
|
||||||
Self::new_with_label_names(Vec::default(), HashMap::default(), Some(num_locals))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an Assembler for outlined code that are not specific to any ISEQ,
|
|
||||||
/// e.g. trampolines that are shared globally.
|
|
||||||
pub fn new_without_iseq() -> Self {
|
|
||||||
Self::new_with_label_names(Vec::default(), HashMap::default(), None)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Create an Assembler with parameters that are populated by another Assembler instance.
|
/// Create an Assembler with parameters that are populated by another Assembler instance.
|
||||||
/// This API is used for copying an Assembler for the next compiler pass.
|
/// This API is used for copying an Assembler for the next compiler pass.
|
||||||
pub fn new_with_label_names(label_names: Vec<String>, num_vregs: usize) -> Self {
|
pub fn new_with_label_names(label_names: Vec<String>, num_vregs: usize) -> Self {
|
||||||
|
@ -1273,25 +1202,6 @@ impl Assembler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Get the list of registers that can be used for stack temps.
|
|
||||||
pub fn get_temp_regs2() -> &'static [Reg] {
|
|
||||||
let num_regs = get_option!(num_temp_regs);
|
|
||||||
&TEMP_REGS[0..num_regs]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the number of locals for the ISEQ being compiled
|
|
||||||
pub fn get_num_locals(&self) -> Option<u32> {
|
|
||||||
self.num_locals
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a context for generating side exits
|
|
||||||
pub fn set_side_exit_context(&mut self, pc: *mut VALUE, stack_size: u8) {
|
|
||||||
self.side_exit_pc = Some(pc);
|
|
||||||
self.side_exit_stack_size = Some(stack_size);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Build an Opnd::VReg and initialize its LiveRange
|
/// Build an Opnd::VReg and initialize its LiveRange
|
||||||
pub(super) fn new_vreg(&mut self, num_bits: u8) -> Opnd {
|
pub(super) fn new_vreg(&mut self, num_bits: u8) -> Opnd {
|
||||||
let vreg = Opnd::VReg { idx: self.live_ranges.len(), num_bits };
|
let vreg = Opnd::VReg { idx: self.live_ranges.len(), num_bits };
|
||||||
|
@ -1330,24 +1240,6 @@ impl Assembler
|
||||||
self.insns.push(insn);
|
self.insns.push(insn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Get a cached side exit, wrapping a counter if specified
|
|
||||||
pub fn get_side_exit(&mut self, side_exit_context: &SideExitContext, counter: Option<Counter>, ocb: &mut OutlinedCb) -> Option<CodePtr> {
|
|
||||||
// Get a cached side exit
|
|
||||||
let side_exit = match self.side_exits.get(&side_exit_context) {
|
|
||||||
None => {
|
|
||||||
let exit_code = gen_outlined_exit(side_exit_context.pc, self.num_locals.unwrap(), &side_exit_context.get_ctx(), ocb)?;
|
|
||||||
self.side_exits.insert(*side_exit_context, exit_code);
|
|
||||||
exit_code
|
|
||||||
}
|
|
||||||
Some(code_ptr) => *code_ptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wrap a counter if needed
|
|
||||||
gen_counted_exit(side_exit_context.pc, side_exit, ocb, counter)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Create a new label instance that we can jump to
|
/// Create a new label instance that we can jump to
|
||||||
pub fn new_label(&mut self, name: &str) -> Target
|
pub fn new_label(&mut self, name: &str) -> Target
|
||||||
{
|
{
|
||||||
|
@ -1358,164 +1250,6 @@ impl Assembler
|
||||||
Target::Label(label)
|
Target::Label(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Convert Opnd::Stack to Opnd::Mem or Opnd::Reg
|
|
||||||
pub fn lower_stack_opnd(&self, opnd: &Opnd) -> Opnd {
|
|
||||||
// Convert Opnd::Stack to Opnd::Mem
|
|
||||||
fn mem_opnd(opnd: &Opnd) -> Opnd {
|
|
||||||
if let Opnd::Stack { idx, sp_offset, num_bits, .. } = *opnd {
|
|
||||||
incr_counter!(temp_mem_opnd);
|
|
||||||
Opnd::mem(num_bits, SP, (sp_offset as i32 - idx - 1) * SIZEOF_VALUE_I32)
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert Opnd::Stack to Opnd::Reg
|
|
||||||
fn reg_opnd(opnd: &Opnd, reg_idx: usize) -> Opnd {
|
|
||||||
let regs = Assembler::get_temp_regs2();
|
|
||||||
if let Opnd::Stack { num_bits, .. } = *opnd {
|
|
||||||
incr_counter!(temp_reg_opnd);
|
|
||||||
Opnd::Reg(regs[reg_idx]).with_num_bits(num_bits).unwrap()
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match opnd {
|
|
||||||
Opnd::Stack { reg_mapping, .. } => {
|
|
||||||
if let Some(reg_idx) = reg_mapping.unwrap().get_reg(opnd.reg_opnd()) {
|
|
||||||
reg_opnd(opnd, reg_idx)
|
|
||||||
} else {
|
|
||||||
mem_opnd(opnd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocate a register to a stack temp if available.
|
|
||||||
pub fn alloc_reg(&mut self, mapping: RegOpnd) {
|
|
||||||
// Allocate a register if there's no conflict.
|
|
||||||
let mut reg_mapping = self.ctx.get_reg_mapping();
|
|
||||||
if reg_mapping.alloc_reg(mapping) {
|
|
||||||
self.set_reg_mapping(reg_mapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Erase local variable type information
|
|
||||||
/// eg: because of a call we can't track
|
|
||||||
pub fn clear_local_types(&mut self) {
|
|
||||||
asm_comment!(self, "clear local variable types");
|
|
||||||
self.ctx.clear_local_types();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Repurpose stack temp registers to the corresponding locals for arguments
|
|
||||||
pub fn map_temp_regs_to_args(&mut self, callee_ctx: &mut Context, argc: i32) -> Vec<RegOpnd> {
|
|
||||||
let mut callee_reg_mapping = callee_ctx.get_reg_mapping();
|
|
||||||
let mut mapped_temps = vec![];
|
|
||||||
|
|
||||||
for arg_idx in 0..argc {
|
|
||||||
let stack_idx: u8 = (self.ctx.get_stack_size() as i32 - argc + arg_idx).try_into().unwrap();
|
|
||||||
let temp_opnd = RegOpnd::Stack(stack_idx);
|
|
||||||
|
|
||||||
// For each argument, if the stack temp for it has a register,
|
|
||||||
// let the callee use the register for the local variable.
|
|
||||||
if let Some(reg_idx) = self.ctx.get_reg_mapping().get_reg(temp_opnd) {
|
|
||||||
let local_opnd = RegOpnd::Local(arg_idx.try_into().unwrap());
|
|
||||||
callee_reg_mapping.set_reg(local_opnd, reg_idx);
|
|
||||||
mapped_temps.push(temp_opnd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
asm_comment!(self, "local maps: {:?}", callee_reg_mapping);
|
|
||||||
callee_ctx.set_reg_mapping(callee_reg_mapping);
|
|
||||||
mapped_temps
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spill all live registers to the stack
|
|
||||||
pub fn spill_regs(&mut self) {
|
|
||||||
self.spill_regs_except(&vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spill all live registers except `ignored_temps` to the stack
|
|
||||||
pub fn spill_regs_except(&mut self, ignored_temps: &Vec<RegOpnd>) {
|
|
||||||
// Forget registers above the stack top
|
|
||||||
let mut reg_mapping = self.ctx.get_reg_mapping();
|
|
||||||
for stack_idx in self.ctx.get_stack_size()..MAX_CTX_TEMPS as u8 {
|
|
||||||
reg_mapping.dealloc_reg(RegOpnd::Stack(stack_idx));
|
|
||||||
}
|
|
||||||
self.set_reg_mapping(reg_mapping);
|
|
||||||
|
|
||||||
// If no registers are in use, skip all checks
|
|
||||||
if self.ctx.get_reg_mapping() == RegMapping::default() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect stack temps to be spilled
|
|
||||||
let mut spilled_opnds = vec![];
|
|
||||||
for stack_idx in 0..u8::min(MAX_CTX_TEMPS as u8, self.ctx.get_stack_size()) {
|
|
||||||
let reg_opnd = RegOpnd::Stack(stack_idx);
|
|
||||||
if !ignored_temps.contains(®_opnd) && reg_mapping.dealloc_reg(reg_opnd) {
|
|
||||||
let idx = self.ctx.get_stack_size() - 1 - stack_idx;
|
|
||||||
let spilled_opnd = self.stack_opnd(idx.into());
|
|
||||||
spilled_opnds.push(spilled_opnd);
|
|
||||||
reg_mapping.dealloc_reg(spilled_opnd.reg_opnd());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect locals to be spilled
|
|
||||||
for local_idx in 0..MAX_CTX_TEMPS as u8 {
|
|
||||||
if reg_mapping.dealloc_reg(RegOpnd::Local(local_idx)) {
|
|
||||||
let first_local_ep_offset = self.num_locals.unwrap() + VM_ENV_DATA_SIZE - 1;
|
|
||||||
let ep_offset = first_local_ep_offset - local_idx as u32;
|
|
||||||
let spilled_opnd = self.local_opnd(ep_offset);
|
|
||||||
spilled_opnds.push(spilled_opnd);
|
|
||||||
reg_mapping.dealloc_reg(spilled_opnd.reg_opnd());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spill stack temps and locals
|
|
||||||
if !spilled_opnds.is_empty() {
|
|
||||||
asm_comment!(self, "spill_regs: {:?} -> {:?}", self.ctx.get_reg_mapping(), reg_mapping);
|
|
||||||
for &spilled_opnd in spilled_opnds.iter() {
|
|
||||||
self.spill_reg(spilled_opnd);
|
|
||||||
}
|
|
||||||
self.ctx.set_reg_mapping(reg_mapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Spill a stack temp from a register to the stack
|
|
||||||
pub fn spill_reg(&mut self, opnd: Opnd) {
|
|
||||||
assert_ne!(self.ctx.get_reg_mapping().get_reg(opnd.reg_opnd()), None);
|
|
||||||
|
|
||||||
// Use different RegMappings for dest and src operands
|
|
||||||
let reg_mapping = self.ctx.get_reg_mapping();
|
|
||||||
let mut mem_mappings = reg_mapping;
|
|
||||||
mem_mappings.dealloc_reg(opnd.reg_opnd());
|
|
||||||
|
|
||||||
// Move the stack operand from a register to memory
|
|
||||||
match opnd {
|
|
||||||
Opnd::Stack { idx, num_bits, stack_size, num_locals, sp_offset, .. } => {
|
|
||||||
self.mov(
|
|
||||||
Opnd::Stack { idx, num_bits, stack_size, num_locals, sp_offset, reg_mapping: Some(mem_mappings) },
|
|
||||||
Opnd::Stack { idx, num_bits, stack_size, num_locals, sp_offset, reg_mapping: Some(reg_mapping) },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
incr_counter!(temp_spill);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update which stack temps are in a register
|
|
||||||
pub fn set_reg_mapping(&mut self, reg_mapping: RegMapping) {
|
|
||||||
if self.ctx.get_reg_mapping() != reg_mapping {
|
|
||||||
asm_comment!(self, "reg_mapping: {:?} -> {:?}", self.ctx.get_reg_mapping(), reg_mapping);
|
|
||||||
self.ctx.set_reg_mapping(reg_mapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Shuffle register moves, sometimes adding extra moves using SCRATCH_REG,
|
// Shuffle register moves, sometimes adding extra moves using SCRATCH_REG,
|
||||||
// so that they will not rewrite each other before they are used.
|
// so that they will not rewrite each other before they are used.
|
||||||
pub fn resolve_parallel_moves(old_moves: &Vec<(Reg, Opnd)>) -> Vec<(Reg, Opnd)> {
|
pub fn resolve_parallel_moves(old_moves: &Vec<(Reg, Opnd)>) -> Vec<(Reg, Opnd)> {
|
||||||
|
@ -1813,8 +1547,7 @@ impl Assembler
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compile Target::SideExit and convert it into Target::CodePtr for all instructions
|
/// Compile Target::SideExit and convert it into Target::CodePtr for all instructions
|
||||||
#[must_use]
|
pub fn compile_side_exits(&mut self) {
|
||||||
pub fn compile_side_exits(&mut self) -> Option<()> {
|
|
||||||
let mut targets = HashMap::new();
|
let mut targets = HashMap::new();
|
||||||
for (idx, insn) in self.insns.iter().enumerate() {
|
for (idx, insn) in self.insns.iter().enumerate() {
|
||||||
if let Some(target @ Target::SideExit { .. }) = insn.target() {
|
if let Some(target @ Target::SideExit { .. }) = insn.target() {
|
||||||
|
@ -1825,7 +1558,7 @@ impl Assembler
|
||||||
for (idx, target) in targets {
|
for (idx, target) in targets {
|
||||||
// Compile a side exit. Note that this is past the split pass and alloc_regs(),
|
// Compile a side exit. Note that this is past the split pass and alloc_regs(),
|
||||||
// so you can't use a VReg or an instruction that needs to be split.
|
// so you can't use a VReg or an instruction that needs to be split.
|
||||||
if let Target::SideExit { context, reason, label } = target {
|
if let Target::SideExit { pc, stack, locals, reason, label } = target {
|
||||||
asm_comment!(self, "Exit: {reason}");
|
asm_comment!(self, "Exit: {reason}");
|
||||||
let side_exit_label = if let Some(label) = label {
|
let side_exit_label = if let Some(label) = label {
|
||||||
Target::Label(label)
|
Target::Label(label)
|
||||||
|
@ -1836,27 +1569,25 @@ impl Assembler
|
||||||
|
|
||||||
// Restore the PC and the stack for regular side exits. We don't do this for
|
// Restore the PC and the stack for regular side exits. We don't do this for
|
||||||
// side exits right after JIT-to-JIT calls, which restore them before the call.
|
// side exits right after JIT-to-JIT calls, which restore them before the call.
|
||||||
if let Some(SideExitContext { pc, stack, locals }) = context {
|
asm_comment!(self, "write stack slots: {stack:?}");
|
||||||
asm_comment!(self, "write stack slots: {stack:?}");
|
for (idx, &opnd) in stack.iter().enumerate() {
|
||||||
for (idx, &opnd) in stack.iter().enumerate() {
|
self.store(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), opnd);
|
||||||
self.store(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), opnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
asm_comment!(self, "write locals: {locals:?}");
|
|
||||||
for (idx, &opnd) in locals.iter().enumerate() {
|
|
||||||
self.store(Opnd::mem(64, SP, (-local_size_and_idx_to_ep_offset(locals.len(), idx) - 1) * SIZEOF_VALUE_I32), opnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
asm_comment!(self, "save cfp->pc");
|
|
||||||
self.load_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::const_ptr(pc));
|
|
||||||
self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::Reg(Assembler::SCRATCH_REG));
|
|
||||||
|
|
||||||
asm_comment!(self, "save cfp->sp");
|
|
||||||
self.lea_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::mem(64, SP, stack.len() as i32 * SIZEOF_VALUE_I32));
|
|
||||||
let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP);
|
|
||||||
self.store(cfp_sp, Opnd::Reg(Assembler::SCRATCH_REG));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asm_comment!(self, "write locals: {locals:?}");
|
||||||
|
for (idx, &opnd) in locals.iter().enumerate() {
|
||||||
|
self.store(Opnd::mem(64, SP, (-local_size_and_idx_to_ep_offset(locals.len(), idx) - 1) * SIZEOF_VALUE_I32), opnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
asm_comment!(self, "save cfp->pc");
|
||||||
|
self.load_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::const_ptr(pc));
|
||||||
|
self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::Reg(Assembler::SCRATCH_REG));
|
||||||
|
|
||||||
|
asm_comment!(self, "save cfp->sp");
|
||||||
|
self.lea_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::mem(64, SP, stack.len() as i32 * SIZEOF_VALUE_I32));
|
||||||
|
let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP);
|
||||||
|
self.store(cfp_sp, Opnd::Reg(Assembler::SCRATCH_REG));
|
||||||
|
|
||||||
asm_comment!(self, "exit to the interpreter");
|
asm_comment!(self, "exit to the interpreter");
|
||||||
self.frame_teardown(&[]); // matching the setup in :bb0-prologue:
|
self.frame_teardown(&[]); // matching the setup in :bb0-prologue:
|
||||||
self.mov(C_RET_OPND, Opnd::UImm(Qundef.as_u64()));
|
self.mov(C_RET_OPND, Opnd::UImm(Qundef.as_u64()));
|
||||||
|
@ -1865,7 +1596,6 @@ impl Assembler
|
||||||
*self.insns[idx].target_mut().unwrap() = side_exit_label;
|
*self.insns[idx].target_mut().unwrap() = side_exit_label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1937,31 +1667,6 @@ impl Assembler {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Let vm_check_canary() assert the leafness of this ccall if leaf_ccall is set
|
|
||||||
fn set_stack_canary(&mut self, opnds: &Vec<Opnd>) -> Option<Opnd> {
|
|
||||||
// Use the slot right above the stack top for verifying leafness.
|
|
||||||
let canary_opnd = self.stack_opnd(-1);
|
|
||||||
|
|
||||||
// If the slot is already used, which is a valid optimization to avoid spills,
|
|
||||||
// give up the verification.
|
|
||||||
let canary_opnd = if cfg!(feature = "runtime_checks") && self.leaf_ccall && opnds.iter().all(|opnd|
|
|
||||||
opnd.get_reg_opnd() != canary_opnd.get_reg_opnd()
|
|
||||||
) {
|
|
||||||
asm_comment!(self, "set stack canary");
|
|
||||||
self.mov(canary_opnd, vm_stack_canary().into());
|
|
||||||
Some(canary_opnd)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Avoid carrying the flag to the next instruction whether we verified it or not.
|
|
||||||
self.leaf_ccall = false;
|
|
||||||
|
|
||||||
canary_opnd
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
pub fn cmp(&mut self, left: Opnd, right: Opnd) {
|
pub fn cmp(&mut self, left: Opnd, right: Opnd) {
|
||||||
self.push_insn(Insn::Cmp { left, right });
|
self.push_insn(Insn::Cmp { left, right });
|
||||||
}
|
}
|
||||||
|
@ -1975,10 +1680,6 @@ impl Assembler {
|
||||||
|
|
||||||
pub fn cpop_all(&mut self) {
|
pub fn cpop_all(&mut self) {
|
||||||
self.push_insn(Insn::CPopAll);
|
self.push_insn(Insn::CPopAll);
|
||||||
|
|
||||||
// Re-enable ccall's RegMappings assertion disabled by cpush_all.
|
|
||||||
// cpush_all + cpop_all preserve all stack temp registers, so it's safe.
|
|
||||||
//self.set_reg_mapping(self.ctx.get_reg_mapping());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cpop_into(&mut self, opnd: Opnd) {
|
pub fn cpop_into(&mut self, opnd: Opnd) {
|
||||||
|
@ -1991,12 +1692,6 @@ impl Assembler {
|
||||||
|
|
||||||
pub fn cpush_all(&mut self) {
|
pub fn cpush_all(&mut self) {
|
||||||
self.push_insn(Insn::CPushAll);
|
self.push_insn(Insn::CPushAll);
|
||||||
|
|
||||||
// Mark all temps as not being in registers.
|
|
||||||
// Temps will be marked back as being in registers by cpop_all.
|
|
||||||
// We assume that cpush_all + cpop_all are used for C functions in utils.rs
|
|
||||||
// that don't require spill_regs for GC.
|
|
||||||
//self.set_reg_mapping(RegMapping::default());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cret(&mut self, opnd: Opnd) {
|
pub fn cret(&mut self, opnd: Opnd) {
|
||||||
|
@ -2257,18 +1952,6 @@ impl Assembler {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Verify the leafness of the given block
|
|
||||||
pub fn with_leaf_ccall<F, R>(&mut self, mut block: F) -> R
|
|
||||||
where F: FnMut(&mut Self) -> R {
|
|
||||||
let old_leaf_ccall = self.leaf_ccall;
|
|
||||||
self.leaf_ccall = true;
|
|
||||||
let ret = block(self);
|
|
||||||
self.leaf_ccall = old_leaf_ccall;
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Add a label at the current position
|
/// Add a label at the current position
|
||||||
pub fn write_label(&mut self, target: Target) {
|
pub fn write_label(&mut self, target: Target) {
|
||||||
assert!(target.unwrap_label().0 < self.label_names.len());
|
assert!(target.unwrap_label().0 < self.label_names.len());
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/*
|
|
||||||
#![cfg(test)]
|
#![cfg(test)]
|
||||||
use crate::asm::CodeBlock;
|
use crate::asm::CodeBlock;
|
||||||
use crate::backend::*;
|
use crate::backend::*;
|
||||||
|
@ -328,5 +327,3 @@ fn test_no_pos_marker_callback_when_compile_fails() {
|
||||||
let cb = &mut CodeBlock::new_dummy(8);
|
let cb = &mut CodeBlock::new_dummy(8);
|
||||||
assert!(asm.compile(cb, None).is_none(), "should fail due to tiny size limit");
|
assert!(asm.compile(cb, None).is_none(), "should fail due to tiny size limit");
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
|
@ -895,7 +895,7 @@ impl Assembler
|
||||||
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Option<(CodePtr, Vec<CodePtr>)> {
|
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Option<(CodePtr, Vec<CodePtr>)> {
|
||||||
let asm = self.x86_split();
|
let asm = self.x86_split();
|
||||||
let mut asm = asm.alloc_regs(regs)?;
|
let mut asm = asm.alloc_regs(regs)?;
|
||||||
asm.compile_side_exits()?;
|
asm.compile_side_exits();
|
||||||
|
|
||||||
// Create label instances in the code block
|
// Create label instances in the code block
|
||||||
for (idx, name) in asm.label_names.iter().enumerate() {
|
for (idx, name) in asm.label_names.iter().enumerate() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_ise
|
||||||
use crate::state::ZJITState;
|
use crate::state::ZJITState;
|
||||||
use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::compile_time_ns};
|
use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::compile_time_ns};
|
||||||
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
|
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
|
||||||
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, SideExitContext, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SP};
|
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SP};
|
||||||
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType, SideExitReason, SideExitReason::*, SpecialObjectType, SELF_PARAM_IDX};
|
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType, SideExitReason, SideExitReason::*, SpecialObjectType, SELF_PARAM_IDX};
|
||||||
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
|
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
|
||||||
use crate::hir_type::{types, Type};
|
use crate::hir_type::{types, Type};
|
||||||
|
@ -337,6 +337,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
|
||||||
Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)),
|
Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)),
|
||||||
Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)),
|
Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)),
|
||||||
Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state))?,
|
Insn::StringConcat { strings, state } => gen_string_concat(jit, asm, opnds!(strings), &function.frame_state(*state))?,
|
||||||
|
Insn::StringIntern { val, state } => gen_intern(asm, opnd!(val), &function.frame_state(*state))?,
|
||||||
Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"),
|
Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"),
|
||||||
Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment
|
Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment
|
||||||
Insn::Jump(branch) => return gen_jump(jit, asm, branch),
|
Insn::Jump(branch) => return gen_jump(jit, asm, branch),
|
||||||
|
@ -378,6 +379,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
|
||||||
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?,
|
Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?,
|
||||||
Insn::Defined { op_type, obj, pushval, v, state } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v), &function.frame_state(*state))?,
|
Insn::Defined { op_type, obj, pushval, v, state } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v), &function.frame_state(*state))?,
|
||||||
&Insn::IncrCounter(counter) => return Some(gen_incr_counter(asm, counter)),
|
&Insn::IncrCounter(counter) => return Some(gen_incr_counter(asm, counter)),
|
||||||
|
Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state))?,
|
||||||
Insn::ArrayExtend { .. }
|
Insn::ArrayExtend { .. }
|
||||||
| Insn::ArrayMax { .. }
|
| Insn::ArrayMax { .. }
|
||||||
| Insn::ArrayPush { .. }
|
| Insn::ArrayPush { .. }
|
||||||
|
@ -386,9 +388,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
|
||||||
| Insn::FixnumMod { .. }
|
| Insn::FixnumMod { .. }
|
||||||
| Insn::HashDup { .. }
|
| Insn::HashDup { .. }
|
||||||
| Insn::NewHash { .. }
|
| Insn::NewHash { .. }
|
||||||
| Insn::ObjToString { .. }
|
|
||||||
| Insn::Send { .. }
|
| Insn::Send { .. }
|
||||||
| Insn::StringIntern { .. }
|
|
||||||
| Insn::Throw { .. }
|
| Insn::Throw { .. }
|
||||||
| Insn::ToArray { .. }
|
| Insn::ToArray { .. }
|
||||||
| Insn::ToNewArray { .. }
|
| Insn::ToNewArray { .. }
|
||||||
|
@ -445,6 +445,24 @@ fn gen_get_ep(asm: &mut Assembler, level: u32) -> Opnd {
|
||||||
ep_opnd
|
ep_opnd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn gen_objtostring(jit: &mut JITState, asm: &mut Assembler, val: Opnd, cd: *const rb_call_data, state: &FrameState) -> Option<Opnd> {
|
||||||
|
gen_prepare_non_leaf_call(jit, asm, state)?;
|
||||||
|
|
||||||
|
let iseq_opnd = Opnd::Value(jit.iseq.into());
|
||||||
|
|
||||||
|
// TODO: Specialize for immediate types
|
||||||
|
// Call rb_vm_objtostring(iseq, recv, cd)
|
||||||
|
let ret = asm_ccall!(asm, rb_vm_objtostring, iseq_opnd, val, (cd as usize).into());
|
||||||
|
|
||||||
|
// TODO: Call `to_s` on the receiver if rb_vm_objtostring returns Qundef
|
||||||
|
// Need to replicate what CALL_SIMPLE_METHOD does
|
||||||
|
asm_comment!(asm, "side-exit if rb_vm_objtostring returns Qundef");
|
||||||
|
asm.cmp(ret, Qundef.into());
|
||||||
|
asm.je(side_exit(jit, state, ObjToStringFallback)?);
|
||||||
|
|
||||||
|
Some(ret)
|
||||||
|
}
|
||||||
|
|
||||||
fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, pushval: VALUE, tested_value: Opnd, state: &FrameState) -> Option<Opnd> {
|
fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, pushval: VALUE, tested_value: Opnd, state: &FrameState) -> Option<Opnd> {
|
||||||
match op_type as defined_type {
|
match op_type as defined_type {
|
||||||
DEFINED_YIELD => {
|
DEFINED_YIELD => {
|
||||||
|
@ -591,6 +609,13 @@ fn gen_getglobal(asm: &mut Assembler, id: ID) -> Opnd {
|
||||||
asm_ccall!(asm, rb_gvar_get, id.0.into())
|
asm_ccall!(asm, rb_gvar_get, id.0.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Intern a string
|
||||||
|
fn gen_intern(asm: &mut Assembler, val: Opnd, state: &FrameState) -> Option<Opnd> {
|
||||||
|
gen_prepare_call_with_gc(asm, state);
|
||||||
|
|
||||||
|
Some(asm_ccall!(asm, rb_str_intern, val))
|
||||||
|
}
|
||||||
|
|
||||||
/// Set global variables
|
/// Set global variables
|
||||||
fn gen_setglobal(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, state: &FrameState) -> Option<()> {
|
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
|
// When trace_var is used, setting a global variable can cause exceptions
|
||||||
|
@ -883,7 +908,7 @@ fn gen_send_without_block_direct(
|
||||||
asm_comment!(asm, "side-exit if callee side-exits");
|
asm_comment!(asm, "side-exit if callee side-exits");
|
||||||
asm.cmp(ret, Qundef.into());
|
asm.cmp(ret, Qundef.into());
|
||||||
// Restore the C stack pointer on exit
|
// Restore the C stack pointer on exit
|
||||||
asm.je(Target::SideExit { context: None, reason: CalleeSideExit, label: None });
|
asm.je(ZJITState::get_exit_code().into());
|
||||||
|
|
||||||
asm_comment!(asm, "restore SP register for the caller");
|
asm_comment!(asm, "restore SP register for the caller");
|
||||||
let new_sp = asm.sub(SP, sp_offset.into());
|
let new_sp = asm.sub(SP, sp_offset.into());
|
||||||
|
@ -1288,6 +1313,7 @@ fn compile_iseq(iseq: IseqPtr) -> Option<Function> {
|
||||||
if !get_option!(disable_hir_opt) {
|
if !get_option!(disable_hir_opt) {
|
||||||
function.optimize();
|
function.optimize();
|
||||||
}
|
}
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
if let Err(err) = function.validate() {
|
if let Err(err) = function.validate() {
|
||||||
debug!("ZJIT: compile_iseq: {err:?}");
|
debug!("ZJIT: compile_iseq: {err:?}");
|
||||||
return None;
|
return None;
|
||||||
|
@ -1313,11 +1339,9 @@ fn build_side_exit(jit: &mut JITState, state: &FrameState, reason: SideExitReaso
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = Target::SideExit {
|
let target = Target::SideExit {
|
||||||
context: Some(SideExitContext {
|
pc: state.pc,
|
||||||
pc: state.pc,
|
stack,
|
||||||
stack,
|
locals,
|
||||||
locals,
|
|
||||||
}),
|
|
||||||
reason,
|
reason,
|
||||||
label,
|
label,
|
||||||
};
|
};
|
||||||
|
@ -1388,7 +1412,7 @@ c_callable! {
|
||||||
if cb.has_dropped_bytes() || payload.status == IseqStatus::CantCompile {
|
if cb.has_dropped_bytes() || payload.status == IseqStatus::CantCompile {
|
||||||
// Exit to the interpreter
|
// Exit to the interpreter
|
||||||
set_pc_and_sp(iseq, ec, sp);
|
set_pc_and_sp(iseq, ec, sp);
|
||||||
return ZJITState::get_stub_exit().raw_ptr(cb);
|
return ZJITState::get_exit_code().raw_ptr(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, attempt to compile the ISEQ. We have to mark_all_executable() beyond this point.
|
// Otherwise, attempt to compile the ISEQ. We have to mark_all_executable() beyond this point.
|
||||||
|
@ -1398,7 +1422,7 @@ c_callable! {
|
||||||
} else {
|
} else {
|
||||||
// Exit to the interpreter
|
// Exit to the interpreter
|
||||||
set_pc_and_sp(iseq, ec, sp);
|
set_pc_and_sp(iseq, ec, sp);
|
||||||
ZJITState::get_stub_exit()
|
ZJITState::get_exit_code()
|
||||||
};
|
};
|
||||||
cb.mark_all_executable();
|
cb.mark_all_executable();
|
||||||
code_ptr.raw_ptr(cb)
|
code_ptr.raw_ptr(cb)
|
||||||
|
@ -1468,12 +1492,12 @@ fn gen_function_stub(cb: &mut CodeBlock, iseq: IseqPtr, branch: Rc<Branch>) -> O
|
||||||
asm.compile(cb)
|
asm.compile(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a trampoline that is used when a function stub fails to compile the ISEQ
|
/// Generate a trampoline that is used when a function exits without restoring PC and the stack
|
||||||
pub fn gen_stub_exit(cb: &mut CodeBlock) -> Option<CodePtr> {
|
pub fn gen_exit(cb: &mut CodeBlock) -> Option<CodePtr> {
|
||||||
let mut asm = Assembler::new();
|
let mut asm = Assembler::new();
|
||||||
|
|
||||||
asm_comment!(asm, "exit from function stub");
|
asm_comment!(asm, "exit from function stub");
|
||||||
asm.frame_teardown(lir::JIT_PRESERVED_REGS);
|
asm.frame_teardown(&[]); // matching the setup in :bb0-prologue:
|
||||||
asm.cret(Qundef.into());
|
asm.cret(Qundef.into());
|
||||||
|
|
||||||
asm.compile(cb).map(|(code_ptr, gc_offsets)| {
|
asm.compile(cb).map(|(code_ptr, gc_offsets)| {
|
||||||
|
|
|
@ -159,6 +159,7 @@ unsafe extern "C" {
|
||||||
pub fn rb_vm_stack_canary() -> VALUE;
|
pub fn rb_vm_stack_canary() -> VALUE;
|
||||||
pub fn rb_vm_push_cfunc_frame(cme: *const rb_callable_method_entry_t, recv_idx: c_int);
|
pub fn rb_vm_push_cfunc_frame(cme: *const rb_callable_method_entry_t, recv_idx: c_int);
|
||||||
pub fn rb_obj_class(klass: VALUE) -> VALUE;
|
pub fn rb_obj_class(klass: VALUE) -> VALUE;
|
||||||
|
pub fn rb_vm_objtostring(iseq: IseqPtr, recv: VALUE, cd: *const rb_call_data) -> VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renames
|
// Renames
|
||||||
|
|
|
@ -414,6 +414,7 @@ pub enum SideExitReason {
|
||||||
GuardBitEquals(VALUE),
|
GuardBitEquals(VALUE),
|
||||||
PatchPoint(Invariant),
|
PatchPoint(Invariant),
|
||||||
CalleeSideExit,
|
CalleeSideExit,
|
||||||
|
ObjToStringFallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for SideExitReason {
|
impl std::fmt::Display for SideExitReason {
|
||||||
|
@ -444,7 +445,7 @@ pub enum Insn {
|
||||||
Param { idx: usize },
|
Param { idx: usize },
|
||||||
|
|
||||||
StringCopy { val: InsnId, chilled: bool, state: InsnId },
|
StringCopy { val: InsnId, chilled: bool, state: InsnId },
|
||||||
StringIntern { val: InsnId },
|
StringIntern { val: InsnId, state: InsnId },
|
||||||
StringConcat { strings: Vec<InsnId>, state: InsnId },
|
StringConcat { strings: Vec<InsnId>, state: InsnId },
|
||||||
|
|
||||||
/// Put special object (VMCORE, CBASE, etc.) based on value_type
|
/// Put special object (VMCORE, CBASE, etc.) based on value_type
|
||||||
|
@ -604,7 +605,9 @@ impl Insn {
|
||||||
Insn::Param { .. } => false,
|
Insn::Param { .. } => false,
|
||||||
Insn::StringCopy { .. } => false,
|
Insn::StringCopy { .. } => false,
|
||||||
Insn::NewArray { .. } => 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::NewRange { .. } => false,
|
||||||
Insn::ArrayDup { .. } => false,
|
Insn::ArrayDup { .. } => false,
|
||||||
Insn::HashDup { .. } => false,
|
Insn::HashDup { .. } => false,
|
||||||
|
@ -776,6 +779,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
|
||||||
Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
|
Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
|
||||||
Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"),
|
Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"),
|
||||||
Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") },
|
Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") },
|
||||||
|
Insn::StringIntern { val, .. } => { write!(f, "StringIntern {val}") },
|
||||||
Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") },
|
Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") },
|
||||||
Insn::SideExit { reason, .. } => write!(f, "SideExit {reason}"),
|
Insn::SideExit { reason, .. } => write!(f, "SideExit {reason}"),
|
||||||
Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"),
|
Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"),
|
||||||
|
@ -799,7 +803,6 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
|
||||||
write!(f, ", {val}")
|
write!(f, ", {val}")
|
||||||
}
|
}
|
||||||
Insn::IncrCounter(counter) => write!(f, "IncrCounter {counter:?}"),
|
Insn::IncrCounter(counter) => write!(f, "IncrCounter {counter:?}"),
|
||||||
insn => { write!(f, "{insn:?}") }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1145,7 +1148,7 @@ impl Function {
|
||||||
&Return { val } => Return { val: find!(val) },
|
&Return { val } => Return { val: find!(val) },
|
||||||
&Throw { throw_state, val } => Throw { throw_state, val: find!(val) },
|
&Throw { throw_state, val } => Throw { throw_state, val: find!(val) },
|
||||||
&StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state },
|
&StringCopy { val, chilled, state } => StringCopy { val: find!(val), chilled, state },
|
||||||
&StringIntern { val } => StringIntern { val: find!(val) },
|
&StringIntern { val, state } => StringIntern { val: find!(val), state: find!(state) },
|
||||||
&StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) },
|
&StringConcat { ref strings, state } => StringConcat { strings: find_vec!(strings), state: find!(state) },
|
||||||
&Test { val } => Test { val: find!(val) },
|
&Test { val } => Test { val: find!(val) },
|
||||||
&IsNil { val } => IsNil { val: find!(val) },
|
&IsNil { val } => IsNil { val: find!(val) },
|
||||||
|
@ -1269,7 +1272,7 @@ impl Function {
|
||||||
Insn::IsNil { val } if !self.type_of(*val).could_be(types::NilClass) => Type::from_cbool(false),
|
Insn::IsNil { val } if !self.type_of(*val).could_be(types::NilClass) => Type::from_cbool(false),
|
||||||
Insn::IsNil { .. } => types::CBool,
|
Insn::IsNil { .. } => types::CBool,
|
||||||
Insn::StringCopy { .. } => types::StringExact,
|
Insn::StringCopy { .. } => types::StringExact,
|
||||||
Insn::StringIntern { .. } => types::StringExact,
|
Insn::StringIntern { .. } => types::Symbol,
|
||||||
Insn::StringConcat { .. } => types::StringExact,
|
Insn::StringConcat { .. } => types::StringExact,
|
||||||
Insn::NewArray { .. } => types::ArrayExact,
|
Insn::NewArray { .. } => types::ArrayExact,
|
||||||
Insn::ArrayDup { .. } => types::ArrayExact,
|
Insn::ArrayDup { .. } => types::ArrayExact,
|
||||||
|
@ -1613,13 +1616,12 @@ impl Function {
|
||||||
self.insn_types[replacement.0] = self.infer_type(replacement);
|
self.insn_types[replacement.0] = self.infer_type(replacement);
|
||||||
self.make_equal_to(insn_id, replacement);
|
self.make_equal_to(insn_id, replacement);
|
||||||
}
|
}
|
||||||
Insn::ObjToString { val, cd, state, .. } => {
|
Insn::ObjToString { val, .. } => {
|
||||||
if self.is_a(val, types::String) {
|
if self.is_a(val, types::String) {
|
||||||
// behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined
|
// behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined
|
||||||
self.make_equal_to(insn_id, val);
|
self.make_equal_to(insn_id, val);
|
||||||
} else {
|
} else {
|
||||||
let replacement = self.push_insn(block, Insn::SendWithoutBlock { self_val: val, cd, args: vec![], state });
|
self.push_insn_id(block, insn_id);
|
||||||
self.make_equal_to(insn_id, replacement)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Insn::AnyToString { str, .. } => {
|
Insn::AnyToString { str, .. } => {
|
||||||
|
@ -1904,7 +1906,6 @@ impl Function {
|
||||||
worklist.extend(strings);
|
worklist.extend(strings);
|
||||||
worklist.push_back(state);
|
worklist.push_back(state);
|
||||||
}
|
}
|
||||||
| &Insn::StringIntern { val }
|
|
||||||
| &Insn::Return { val }
|
| &Insn::Return { val }
|
||||||
| &Insn::Throw { val, .. }
|
| &Insn::Throw { val, .. }
|
||||||
| &Insn::Defined { v: val, .. }
|
| &Insn::Defined { v: val, .. }
|
||||||
|
@ -1913,6 +1914,7 @@ impl Function {
|
||||||
| &Insn::IsNil { val } =>
|
| &Insn::IsNil { val } =>
|
||||||
worklist.push_back(val),
|
worklist.push_back(val),
|
||||||
&Insn::SetGlobal { val, state, .. }
|
&Insn::SetGlobal { val, state, .. }
|
||||||
|
| &Insn::StringIntern { val, state }
|
||||||
| &Insn::StringCopy { val, state, .. }
|
| &Insn::StringCopy { val, state, .. }
|
||||||
| &Insn::GuardType { val, state, .. }
|
| &Insn::GuardType { val, state, .. }
|
||||||
| &Insn::GuardBitEquals { val, state, .. }
|
| &Insn::GuardBitEquals { val, state, .. }
|
||||||
|
@ -2813,7 +2815,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
|
||||||
YARVINSN_putself => { state.stack_push(self_param); }
|
YARVINSN_putself => { state.stack_push(self_param); }
|
||||||
YARVINSN_intern => {
|
YARVINSN_intern => {
|
||||||
let val = state.stack_pop()?;
|
let val = state.stack_pop()?;
|
||||||
let insn_id = fun.push_insn(block, Insn::StringIntern { val });
|
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
|
||||||
|
let insn_id = fun.push_insn(block, Insn::StringIntern { val, state: exit_id });
|
||||||
state.stack_push(insn_id);
|
state.stack_push(insn_id);
|
||||||
}
|
}
|
||||||
YARVINSN_concatstrings => {
|
YARVINSN_concatstrings => {
|
||||||
|
@ -4494,6 +4497,26 @@ mod tests {
|
||||||
"#]]);
|
"#]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intern_interpolated_symbol() {
|
||||||
|
eval(r#"
|
||||||
|
def test
|
||||||
|
:"foo#{123}"
|
||||||
|
end
|
||||||
|
"#);
|
||||||
|
assert_method_hir_with_opcode("test", YARVINSN_intern, expect![[r#"
|
||||||
|
fn test@<compiled>:3:
|
||||||
|
bb0(v0:BasicObject):
|
||||||
|
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
|
||||||
|
v3:Fixnum[123] = Const Value(123)
|
||||||
|
v5:BasicObject = ObjToString v3
|
||||||
|
v7:String = AnyToString v3, str: v5
|
||||||
|
v9:StringExact = StringConcat v2, v7
|
||||||
|
v11:Symbol = StringIntern v9
|
||||||
|
Return v11
|
||||||
|
"#]]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn different_objects_get_addresses() {
|
fn different_objects_get_addresses() {
|
||||||
eval("def test = unknown_method([0], [1], '2', '2')");
|
eval("def test = unknown_method([0], [1], '2', '2')");
|
||||||
|
@ -6089,7 +6112,7 @@ mod opt_tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_eliminate_new_hash_with_elements() {
|
fn test_no_eliminate_new_hash_with_elements() {
|
||||||
eval("
|
eval("
|
||||||
def test(aval, bval)
|
def test(aval, bval)
|
||||||
c = {a: aval, b: bval}
|
c = {a: aval, b: bval}
|
||||||
|
@ -6099,6 +6122,10 @@ mod opt_tests {
|
||||||
assert_optimized_method_hir("test", expect![[r#"
|
assert_optimized_method_hir("test", expect![[r#"
|
||||||
fn test@<compiled>:3:
|
fn test@<compiled>:3:
|
||||||
bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject):
|
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)
|
v9:Fixnum[5] = Const Value(5)
|
||||||
Return v9
|
Return v9
|
||||||
"#]]);
|
"#]]);
|
||||||
|
@ -7235,8 +7262,8 @@ mod opt_tests {
|
||||||
bb0(v0:BasicObject):
|
bb0(v0:BasicObject):
|
||||||
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
|
v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
|
||||||
v3:Fixnum[1] = Const Value(1)
|
v3:Fixnum[1] = Const Value(1)
|
||||||
v11:BasicObject = SendWithoutBlock v3, :to_s
|
v5:BasicObject = ObjToString v3
|
||||||
v7:String = AnyToString v3, str: v11
|
v7:String = AnyToString v3, str: v5
|
||||||
v9:StringExact = StringConcat v2, v7
|
v9:StringExact = StringConcat v2, v7
|
||||||
Return v9
|
Return v9
|
||||||
"#]]);
|
"#]]);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::codegen::gen_stub_exit;
|
use crate::codegen::gen_exit;
|
||||||
use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insns_count, EcPtr, Qnil, VALUE};
|
use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insns_count, EcPtr, Qnil, VALUE};
|
||||||
use crate::cruby_methods;
|
use crate::cruby_methods;
|
||||||
use crate::invariants::Invariants;
|
use crate::invariants::Invariants;
|
||||||
|
@ -33,8 +33,8 @@ pub struct ZJITState {
|
||||||
/// Properties of core library methods
|
/// Properties of core library methods
|
||||||
method_annotations: cruby_methods::Annotations,
|
method_annotations: cruby_methods::Annotations,
|
||||||
|
|
||||||
/// Side-exit trampoline used when it fails to compile the ISEQ for a function stub
|
/// Trampoline to side-exit without restoring PC or the stack
|
||||||
stub_exit: CodePtr,
|
exit_code: CodePtr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Private singleton instance of the codegen globals
|
/// Private singleton instance of the codegen globals
|
||||||
|
@ -83,7 +83,7 @@ impl ZJITState {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
let mut cb = CodeBlock::new_dummy();
|
let mut cb = CodeBlock::new_dummy();
|
||||||
|
|
||||||
let stub_exit = gen_stub_exit(&mut cb).unwrap();
|
let exit_code = gen_exit(&mut cb).unwrap();
|
||||||
|
|
||||||
// Initialize the codegen globals instance
|
// Initialize the codegen globals instance
|
||||||
let zjit_state = ZJITState {
|
let zjit_state = ZJITState {
|
||||||
|
@ -92,7 +92,7 @@ impl ZJITState {
|
||||||
invariants: Invariants::default(),
|
invariants: Invariants::default(),
|
||||||
assert_compiles: false,
|
assert_compiles: false,
|
||||||
method_annotations: cruby_methods::init(),
|
method_annotations: cruby_methods::init(),
|
||||||
stub_exit,
|
exit_code,
|
||||||
};
|
};
|
||||||
unsafe { ZJIT_STATE = Some(zjit_state); }
|
unsafe { ZJIT_STATE = Some(zjit_state); }
|
||||||
}
|
}
|
||||||
|
@ -169,9 +169,9 @@ impl ZJITState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a code pointer to the side-exit trampoline for function stubs
|
/// Return a code pointer to the side-exit trampoline
|
||||||
pub fn get_stub_exit() -> CodePtr {
|
pub fn get_exit_code() -> CodePtr {
|
||||||
ZJITState::get_instance().stub_exit
|
ZJITState::get_instance().exit_code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue