diff --git a/.gdbinit b/.gdbinit
index 4457f6f12b087d..f045b12fb0f913 100644
--- a/.gdbinit
+++ b/.gdbinit
@@ -983,49 +983,35 @@ define print_lineno
set $index = 0
set $size = $iseq->body->insns_info.size
set $table = $iseq->body->insns_info.body
- set $positions = $iseq->body->insns_info.positions
#printf "size: %d\n", $size
if $size == 0
else
if $size == 1
printf "%d", $table[0].line_no
else
- if $positions
- # get_insn_info_linear_search
- set $index = 1
- while $index < $size
- #printf "table[%d]: position: %d, line: %d, pos: %d\n", $i, $positions[$i], $table[$i].line_no, $pos
- if $positions[$index] > $pos
- loop_break
- end
- set $index = $index + 1
- if $positions[$index] == $pos
- loop_break
- end
- end
+ # get_insn_info_succinct_bitvector (VM_INSN_INFO_TABLE_IMPL == 2).
+ # insns_info.positions and insns_info.succ_index_table share a union, and
+ # an iseq running on the stack always has succ_index_table.
+ set $sd = $iseq->body->insns_info.positions_or_succ_index_table.succ_index_table
+ set $immediate_table_size = sizeof($sd->imm_part) / sizeof(uint64_t) * 9
+ if $pos < $immediate_table_size
+ set $i = $pos / 9
+ set $j = $pos % 9
+ set $index = ((int)($sd->imm_part[$i] >> ($j * 7))) & 0x7f
else
- # get_insn_info_succinct_bitvector
- set $sd = $iseq->body->insns_info.succ_index_table
- set $immediate_table_size = sizeof($sd->imm_part) / sizeof(uint64_t) * 9
- if $pos < $immediate_table_size
- set $i = $pos / 9
- set $j = $pos % 9
- set $index = ((int)($sd->imm_part[$i] >> ($j * 7))) & 0x7f
- else
- set $block_index = ($pos - $immediate_table_size) / 512
- set $block = &$sd->succ_part[$block_index]
- set $block_bit_index = ($pos - $immediate_table_size) % 512
- set $small_block_index = $block_bit_index / 64
- set $small_block_popcount = $small_block_index == 0 ? 0 : (((int)($block->small_block_ranks >> (($small_block_index - 1) * 9))) & 0x1ff)
- set $x = $block->bits[$small_block_index] << (63 - $block_bit_index % 64)
- set $x = ($x & 0x5555555555555555) + ($x >> 1 & 0x5555555555555555)
- set $x = ($x & 0x3333333333333333) + ($x >> 2 & 0x3333333333333333)
- set $x = ($x & 0x0707070707070707) + ($x >> 4 & 0x0707070707070707)
- set $x = ($x & 0x001f001f001f001f) + ($x >> 8 & 0x001f001f001f001f)
- set $x = ($x & 0x0000003f0000003f) + ($x >>16 & 0x0000003f0000003f)
- set $popcnt = ($x & 0x7f) + ($x >>32 & 0x7f)
- set $index = $block->rank + $small_block_popcount + $popcnt
- end
+ set $block_index = ($pos - $immediate_table_size) / 512
+ set $block = &$sd->succ_part[$block_index]
+ set $block_bit_index = ($pos - $immediate_table_size) % 512
+ set $small_block_index = $block_bit_index / 64
+ set $small_block_popcount = $small_block_index == 0 ? 0 : (((int)($block->small_block_ranks >> (($small_block_index - 1) * 9))) & 0x1ff)
+ set $x = $block->bits[$small_block_index] << (63 - $block_bit_index % 64)
+ set $x = ($x & 0x5555555555555555) + ($x >> 1 & 0x5555555555555555)
+ set $x = ($x & 0x3333333333333333) + ($x >> 2 & 0x3333333333333333)
+ set $x = ($x & 0x0707070707070707) + ($x >> 4 & 0x0707070707070707)
+ set $x = ($x & 0x001f001f001f001f) + ($x >> 8 & 0x001f001f001f001f)
+ set $x = ($x & 0x0000003f0000003f) + ($x >>16 & 0x0000003f0000003f)
+ set $popcnt = ($x & 0x7f) + ($x >>32 & 0x7f)
+ set $index = $block->rank + $small_block_popcount + $popcnt
end
printf "%d", $table[$index-1].line_no
end
diff --git a/ast.c b/ast.c
index 721302c0304370..1f08ee2a48b208 100644
--- a/ast.c
+++ b/ast.c
@@ -359,7 +359,7 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script
rb_raise(rb_eRuntimeError, "cannot get AST for ISEQ compiled by prism");
}
- lines = ISEQ_BODY(iseq)->variable.script_lines;
+ lines = ISEQ_SCRIPT_LINES(iseq);
VALUE path = rb_iseq_path(iseq);
int e_option = RSTRING_LEN(path) == 2 && memcmp(RSTRING_PTR(path), "-e", 2) == 0;
diff --git a/compile.c b/compile.c
index 52f64ce0ae1e93..7fd5f8e33d2d38 100644
--- a/compile.c
+++ b/compile.c
@@ -1015,7 +1015,8 @@ rb_iseq_translate_threaded_code(rb_iseq_t *iseq)
VALUE *
rb_iseq_original_iseq(const rb_iseq_t *iseq) /* cold path */
{
- VALUE *original_code = RUBY_ATOMIC_PTR_LOAD(ISEQ_BODY(iseq)->variable.original_iseq);
+ struct rb_iseq_variable *v = ISEQ_VARIABLE(iseq);
+ VALUE *original_code = v ? RUBY_ATOMIC_PTR_LOAD(v->original_iseq) : NULL;
if (original_code) return original_code;
original_code = ALLOC_N(VALUE, ISEQ_BODY(iseq)->iseq_size);
@@ -1037,7 +1038,8 @@ rb_iseq_original_iseq(const rb_iseq_t *iseq) /* cold path */
/* Concurrent callers can each build a copy; publish only fully
* translated code and keep the first one. */
- VALUE *prev = ATOMIC_PTR_CAS(ISEQ_BODY(iseq)->variable.original_iseq,
+ v = rb_iseq_variable_ensure((rb_iseq_t *)iseq);
+ VALUE *prev = ATOMIC_PTR_CAS(v->original_iseq,
NULL, original_code);
if (prev) {
SIZED_FREE_N(original_code, ISEQ_BODY(iseq)->iseq_size);
@@ -1530,7 +1532,7 @@ new_child_iseq(rb_iseq_t *iseq, const NODE *const node,
line_no, parent,
isolated_depth ? isolated_depth + 1 : 0,
type, ISEQ_COMPILE_DATA(iseq)->option,
- ISEQ_BODY(iseq)->variable.script_lines);
+ ISEQ_SCRIPT_LINES(iseq));
debugs("[new_child_iseq]< ---------------------------------------\n");
return ret_iseq;
}
@@ -1700,10 +1702,8 @@ iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
}
#if VM_INSN_INFO_TABLE_IMPL == 2
- if (ISEQ_BODY(iseq)->insns_info.succ_index_table == NULL) {
- debugs("[compile step 7 (rb_iseq_insns_info_encode_positions)] \n");
- rb_iseq_insns_info_encode_positions(iseq);
- }
+ debugs("[compile step 7 (rb_iseq_insns_info_encode_positions)] \n");
+ rb_iseq_insns_info_encode_positions(iseq);
#endif
if (compile_debug > 1) {
@@ -2976,12 +2976,12 @@ iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
/* get rid of memory leak when REALLOC failed */
body->insns_info.body = insns_info;
- body->insns_info.positions = positions;
+ body->insns_info.positions_or_succ_index_table.positions = positions;
SIZED_REALLOC_N(insns_info, struct iseq_insn_info_entry, insns_info_index, insns_info_size);
body->insns_info.body = insns_info;
SIZED_REALLOC_N(positions, unsigned int, insns_info_index, positions_size);
- body->insns_info.positions = positions;
+ body->insns_info.positions_or_succ_index_table.positions = positions;
body->insns_info.size = insns_info_index;
return COMPILE_OK;
@@ -9447,7 +9447,7 @@ compile_builtin_mandatory_only_method(rb_iseq_t *iseq, const NODE *node, const N
rb_iseq_path(iseq), rb_iseq_realpath(iseq),
nd_line(line_node), NULL, 0,
ISEQ_TYPE_METHOD, ISEQ_COMPILE_DATA(iseq)->option,
- ISEQ_BODY(iseq)->variable.script_lines);
+ ISEQ_SCRIPT_LINES(iseq));
RB_OBJ_WRITE(iseq, &ISEQ_BODY(iseq)->mandatory_only_iseq, (VALUE)mandatory_only_iseq);
ALLOCV_END(idtmp);
@@ -13820,7 +13820,7 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq)
ibf_dump_write_small_value(dump, mandatory_only_iseq_index);
ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(ci_entries_offset));
ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(outer_variables_offset));
- ibf_dump_write_small_value(dump, body->variable.flip_count);
+ ibf_dump_write_small_value(dump, ISEQ_FLIP_CNT(iseq));
ibf_dump_write_small_value(dump, body->local_table_size);
ibf_dump_write_small_value(dump, body->ivc_size);
ibf_dump_write_small_value(dump, body->icvarc_size);
@@ -14012,10 +14012,12 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset)
load_body->ci_size = ci_size;
load_body->insns_info.size = insns_info_size;
- ISEQ_COVERAGE_SET(iseq, Qnil);
+ // variable is NULL from ZALLOC; only allocate if flip_count is non-zero.
ISEQ_ORIGINAL_ISEQ_CLEAR(iseq);
- load_body->variable.flip_count = variable_flip_count;
- load_body->variable.script_lines = Qnil;
+ if (variable_flip_count) {
+ struct rb_iseq_variable *v = rb_iseq_variable_ensure(iseq);
+ v->flip_count = variable_flip_count;
+ }
load_body->location.first_lineno = location_first_lineno;
load_body->location.node_id = location_node_id;
@@ -14044,7 +14046,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset)
load_body->param.keyword = ibf_load_param_keyword(load, param_keyword_offset);
load_body->param.flags.has_kw = (param_flags >> 4) & 1;
load_body->insns_info.body = ibf_load_insns_info_body(load, insns_info_body_offset, insns_info_size);
- load_body->insns_info.positions = ibf_load_insns_info_positions(load, insns_info_positions_offset, insns_info_size);
+ load_body->insns_info.positions_or_succ_index_table.positions = ibf_load_insns_info_positions(load, insns_info_positions_offset, insns_info_size);
load_body->local_table = ibf_load_local_table(load, local_table_offset, local_table_size);
load_body->lvar_states = ibf_load_lvar_states(load, lvar_states_offset, local_table_size, load_body->local_table);
ibf_load_catch_table(load, catch_table_offset, catch_table_size, iseq);
@@ -15240,8 +15242,13 @@ rb_iseq_dup_with_independent_caches(const rb_iseq_t *src_root)
struct rb_iseq_constant_body *cb = ISEQ_BODY(copy);
if (!cb->local_iseq) RB_OBJ_WRITE(copy, &cb->local_iseq, sb->local_iseq);
RB_OBJ_WRITE(copy, &cb->location.pathobj, sb->location.pathobj);
- RB_OBJ_WRITE(copy, &cb->variable.script_lines, sb->variable.script_lines);
- ISEQ_COVERAGE_SET(copy, ISEQ_COVERAGE(src_root));
+ VALUE sl = ISEQ_SCRIPT_LINES(src_root);
+ VALUE cov = ISEQ_COVERAGE(src_root);
+ if (!NIL_P(sl) || !NIL_P(cov)) {
+ struct rb_iseq_variable *v = rb_iseq_variable_ensure(copy);
+ RB_OBJ_WRITE(copy, &v->script_lines, sl);
+ RB_OBJ_WRITE(copy, &v->coverage, cov);
+ }
if (i == 0) {
RB_OBJ_WRITE(copy, &cb->parent_iseq, sb->parent_iseq);
diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb
index 0512d4aefd35e2..34dc123b5c0f31 100644
--- a/ext/json/lib/json/common.rb
+++ b/ext/json/lib/json/common.rb
@@ -142,7 +142,56 @@ class JSONError < StandardError; end
# This exception is raised if a parser error occurs.
class ParserError < JSONError
- attr_reader :line, :column
+ # Line number where the parser encountered an error.
+ # Is nil when raised by JSON::ResumableParser.
+ attr_reader :line
+
+ # Column number where the parser encountered an error.
+ # Is nil when raised by JSON::ResumableParser.
+ attr_reader :column
+
+ # Returns a best effort JSONPath string representing where in the document
+ # the parser encountered an error:
+ #
+ # begin
+ # JSON.parse('{"articles": [ { "title": invalid } ]}')
+ # rescue JSON::ParserError => error
+ # error.json_path # => "$.articles[0].title"
+ # end
+ def json_path
+ return @json_path if String === @json_path
+
+ if Array === @json_path
+ path = build_json_path(@json_path)
+ @json_path = path unless frozen?
+ return path
+ end
+ end
+
+ private
+
+ def build_json_path(segments)
+ error = false
+ path = segments.filter_map do |segment|
+ next if error
+
+ case segment
+ when Integer
+ "[#{segment}]"
+ when String, Symbol
+ if segment.match?(/\A[a-zA-Z\$\_][a-zA-Z\$\_0-9]*\z/)
+ ".#{segment}"
+ else
+ segment = segment.to_s.gsub(/["\\]/, { '"' => '\\"', '\\' => '\\\\' })
+ %{["#{segment}"]}
+ end
+ else
+ error = true
+ nil
+ end
+ end.join
+ "$#{path}".freeze
+ end
end
# This exception is raised if the nesting of parsed data structures is too
diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c
index f10e2260ad7b75..fc6a7bbf62f626 100644
--- a/ext/json/parser/parser.c
+++ b/ext/json/parser/parser.c
@@ -5,7 +5,7 @@
static VALUE mJSON, eNestingError, eParserError, Encoding_UTF_8;
static VALUE CNaN, CInfinity, CMinusInfinity, JSON_empty_string;
-static ID i_new, i_try_convert, i_encode, i_at_line, i_at_column;
+static ID i_new, i_try_convert, i_encode, i_at_line, i_at_column, i_at_json_path;
#ifndef HAVE_RB_STR_TO_INTERNED_STR
static ID i_uminus;
#endif
@@ -651,11 +651,42 @@ static VALUE build_parse_error_message(const char *format, JSON_ParserState *sta
return rb_enc_sprintf(enc_utf8, format, ptr);
}
+static VALUE json_path_new(JSON_ParserState *state, VALUE duplicate_key)
+{
+ VALUE path = rb_ary_new_capa(state->current_nesting);
+
+ json_frame_stack *frames = state->frames;
+ rvalue_stack *values = state->value_stack;
+
+ for (long depth = 1; depth < frames->head; depth++) {
+ json_frame *frame = &frames->ptr[depth];
+
+ bool innermost = depth == frames->head - 1;
+ long child_head = innermost ? values->head : frames->ptr[depth + 1].value_stack_head;
+ long count = child_head - frame->value_stack_head;
+
+ if (frame->type == JSON_FRAME_ARRAY) {
+ rb_ary_push(path, LONG2NUM(frame->phase == JSON_PHASE_ARRAY_COMMA ? count - 1 : count));
+ } else if (innermost && !UNDEF_P(duplicate_key)) {
+ rb_ary_push(path, duplicate_key);
+ } else if (count & 1) {
+ rb_ary_push(path, values->ptr[child_head - 1]);
+ } else if (frame->phase == JSON_PHASE_OBJECT_COMMA && count >= 2) {
+ rb_ary_push(path, values->ptr[child_head - 2]);
+ } else {
+ break;
+ }
+ }
+
+ return path;
+}
+
static VALUE parse_error_new(JSON_ParserState *state, VALUE message, long line, long column, bool eos)
{
VALUE exc = rb_exc_new_str(eParserError, message);
rb_ivar_set(exc, i_at_line, LONG2NUM(line));
rb_ivar_set(exc, i_at_column, LONG2NUM(column));
+ rb_ivar_set(exc, i_at_json_path, json_path_new(state, Qundef));
return exc;
}
@@ -1199,14 +1230,17 @@ NORETURN(static) void raise_duplicate_key_error(JSON_ParserState *state, VALUE d
);
rb_str_concat(message, build_parse_error_message("", state));
+ VALUE exc;
if (state->parser) { // line and columns can't be accurate in resumable
- rb_exc_raise(parse_error_new(state, message, 0, 0, false));
+ exc = parse_error_new(state, message, 0, 0, false);
} else {
long line, column;
cursor_position(state, &line, &column);
rb_str_catf(message, " at line %ld column %ld", line, column);
- rb_exc_raise(parse_error_new(state, message, line, column, false));
+ exc = parse_error_new(state, message, line, column, false);
}
+ rb_ivar_set(exc, i_at_json_path, json_path_new(state, duplicate_key));
+ rb_exc_raise(exc);
}
NOINLINE(static) void json_on_duplicate_key(JSON_ParserState *state, JSON_ParserConfig *config, size_t count, const VALUE *pairs)
@@ -2122,6 +2156,12 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
// the rvalue stack.
VALUE result = complete ? *rvalue_stack_peek(state->value_stack, 1) : Qundef;
+ if (complete) {
+ json_ensure_eof(state, config);
+ } else {
+ raise_eos_error("unexpected end of input", state);
+ }
+
// This may be skipped in case of exception, but
// it won't cause a leak.
rvalue_stack_eagerly_release(value_stack_handle);
@@ -2130,12 +2170,6 @@ static VALUE cParser_parse(JSON_ParserConfig *config, VALUE src)
RB_GC_GUARD(frame_stack_handle);
RB_GC_GUARD(Vsource);
- if (complete) {
- json_ensure_eof(state, config);
- } else {
- raise_eos_error("unexpected end of input", state);
- }
-
return result;
}
@@ -2871,6 +2905,7 @@ void Init_parser(void)
i_encode = rb_intern("encode");
i_at_line = rb_intern("@line");
i_at_column = rb_intern("@column");
+ i_at_json_path = rb_intern("@json_path");
binary_encindex = rb_ascii8bit_encindex();
utf8_encindex = rb_utf8_encindex();
diff --git a/include/ruby/io/buffer.h b/include/ruby/io/buffer.h
index 0d6b52e2698e82..38c6bfcf3eded3 100644
--- a/include/ruby/io/buffer.h
+++ b/include/ruby/io/buffer.h
@@ -29,6 +29,9 @@ RUBY_EXTERN VALUE rb_cIOBuffer;
// The operating system page size.
RUBY_EXTERN size_t RUBY_IO_BUFFER_PAGE_SIZE;
+// The alignment required for file mapping offsets.
+RUBY_EXTERN size_t RUBY_IO_BUFFER_MAP_ALIGNMENT;
+
// The default buffer size, usually a (small) multiple of the page size.
// Can be overridden by the RUBY_IO_BUFFER_DEFAULT_SIZE environment variable.
RUBY_EXTERN size_t RUBY_IO_BUFFER_DEFAULT_SIZE;
diff --git a/io_buffer.c b/io_buffer.c
index 54896098751cec..5a5128aa1dd949 100644
--- a/io_buffer.c
+++ b/io_buffer.c
@@ -30,6 +30,7 @@ VALUE rb_eIOBufferInvalidatedError;
VALUE rb_eIOBufferMaskError;
size_t RUBY_IO_BUFFER_PAGE_SIZE;
+size_t RUBY_IO_BUFFER_MAP_ALIGNMENT;
size_t RUBY_IO_BUFFER_DEFAULT_SIZE;
#ifdef _WIN32
@@ -818,6 +819,19 @@ rb_io_buffer_new_locked(void *base, size_t size, enum rb_io_buffer_flags flags)
VALUE
rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags flags)
{
+ if (UNLIKELY(offset < 0)) {
+ rb_raise(rb_eArgError,
+ "Offset (%" PRIsVALUE ") can't be negative!",
+ OFFT2NUM(offset));
+ }
+
+ if (UNLIKELY((uintmax_t)offset % RUBY_IO_BUFFER_MAP_ALIGNMENT != 0)) {
+ rb_raise(rb_eArgError,
+ "Offset (%" PRIsVALUE ") must be a multiple of IO::Buffer::MAP_ALIGNMENT (%" PRIuSIZE ")!",
+ OFFT2NUM(offset),
+ RUBY_IO_BUFFER_MAP_ALIGNMENT);
+ }
+
VALUE instance = rb_io_buffer_type_allocate(rb_cIOBuffer);
struct rb_io_buffer *buffer = get_io_buffer(instance);
@@ -835,9 +849,10 @@ rb_io_buffer_map(VALUE io, size_t size, rb_off_t offset, enum rb_io_buffer_flags
* Create an IO::Buffer for reading from +file+ by memory-mapping the file.
* +file+ should be a +File+ instance, opened for reading or reading and writing.
*
- * Optional +size+ and +offset+ of mapping can be specified.
- * Trying to map an empty file or specify +size+ of 0 will raise an error.
- * Valid values for +offset+ are system-dependent.
+ * Optional +size+ and +offset+ of mapping can be specified. The +offset+ must
+ * be a multiple of IO::Buffer::MAP_ALIGNMENT. The +size+ does not need to be
+ * aligned. Trying to map an empty file or specify +size+ of 0 will raise an
+ * error.
*
* By default, the buffer is writable and expects the file to be writable.
* It is also shared, so several processes can use the same mapping.
@@ -937,10 +952,9 @@ io_buffer_map(int argc, VALUE *argv, VALUE klass)
size = (size_t)(file_size - offset);
}
else if (UNLIKELY((size_t)(file_size - offset) < size)) {
- size_t maximum_page_count =
- (file_size - size) / RUBY_IO_BUFFER_PAGE_SIZE;
size_t maximum_offset =
- RUBY_IO_BUFFER_PAGE_SIZE * maximum_page_count;
+ (file_size - size) / RUBY_IO_BUFFER_MAP_ALIGNMENT *
+ RUBY_IO_BUFFER_MAP_ALIGNMENT;
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be larger than "
"%" PRIuSIZE " for requested size (%" PRIuSIZE ")",
@@ -4353,8 +4367,10 @@ Init_IO_Buffer(void)
SYSTEM_INFO info;
GetSystemInfo(&info);
RUBY_IO_BUFFER_PAGE_SIZE = info.dwPageSize;
+ RUBY_IO_BUFFER_MAP_ALIGNMENT = info.dwAllocationGranularity;
#else /* not WIN32 */
RUBY_IO_BUFFER_PAGE_SIZE = sysconf(_SC_PAGESIZE);
+ RUBY_IO_BUFFER_MAP_ALIGNMENT = RUBY_IO_BUFFER_PAGE_SIZE;
#endif
RUBY_IO_BUFFER_DEFAULT_SIZE = io_buffer_default_size(RUBY_IO_BUFFER_PAGE_SIZE);
@@ -4362,6 +4378,9 @@ Init_IO_Buffer(void)
/* The operating system page size. Used for efficient page-aligned memory allocations. */
rb_define_const(rb_cIOBuffer, "PAGE_SIZE", SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE));
+ /* The alignment required for file mapping offsets. Mapping sizes do not need to be aligned. */
+ rb_define_const(rb_cIOBuffer, "MAP_ALIGNMENT", SIZET2NUM(RUBY_IO_BUFFER_MAP_ALIGNMENT));
+
/* The default buffer size, typically a (small) multiple of the PAGE_SIZE.
Can be explicitly specified by setting the RUBY_IO_BUFFER_DEFAULT_SIZE
environment variable. */
diff --git a/iseq.c b/iseq.c
index 80d7324a1ec088..c1f8bc6fb66d8b 100644
--- a/iseq.c
+++ b/iseq.c
@@ -203,9 +203,10 @@ rb_iseq_free(const rb_iseq_t *iseq)
#endif
SIZED_FREE_N(body->iseq_encoded, body->iseq_size);
SIZED_FREE_N(body->insns_info.body, body->insns_info.size);
- SIZED_FREE_N(body->insns_info.positions, body->insns_info.size);
#if VM_INSN_INFO_TABLE_IMPL == 2
- ruby_xfree(body->insns_info.succ_index_table);
+ ruby_xfree(body->insns_info.positions_or_succ_index_table.succ_index_table);
+#else
+ SIZED_FREE_N(body->insns_info.positions_or_succ_index_table.positions, body->insns_info.size);
#endif
SIZED_FREE_N(body->is_entries, ISEQ_IS_SIZE(body));
SIZED_FREE_N(body->call_data, body->ci_size);
@@ -218,6 +219,7 @@ rb_iseq_free(const rb_iseq_t *iseq)
}
ISEQ_ORIGINAL_ISEQ_CLEAR(iseq);
+ if (body->variable) xfree(body->variable);
struct rb_iseq_param_keyword *pkw = (struct rb_iseq_param_keyword *)body->param.keyword;
if (pkw != NULL) {
@@ -241,6 +243,53 @@ rb_iseq_free(const rb_iseq_t *iseq)
RUBY_FREE_LEAVE("iseq");
}
+/* CAS-publish the out-of-lined variable struct: concurrent callers (e.g. two
+ * dumpers hitting rb_iseq_original_iseq on a shared iseq) can each build a
+ * copy, mirroring the original_iseq CAS in compile.c. */
+struct rb_iseq_variable *
+rb_iseq_variable_ensure(rb_iseq_t *iseq)
+{
+ struct rb_iseq_variable *v = ISEQ_VARIABLE(iseq);
+ if (v) return v;
+ v = ALLOC(struct rb_iseq_variable);
+ v->flip_count = 0;
+ v->script_lines = Qnil;
+ v->coverage = Qfalse;
+ v->pc2branchindex = Qfalse;
+ v->original_iseq = NULL;
+ struct rb_iseq_variable *prev = ATOMIC_PTR_CAS(ISEQ_BODY(iseq)->variable, NULL, v);
+ if (prev) {
+ xfree(v);
+ v = prev;
+ }
+ return v;
+}
+
+void
+rb_iseq_coverage_set(rb_iseq_t *iseq, VALUE cov)
+{
+ if (!RTEST(cov) && !ISEQ_VARIABLE(iseq)) return;
+ struct rb_iseq_variable *v = rb_iseq_variable_ensure(iseq);
+ RB_OBJ_WRITE(iseq, &v->coverage, cov);
+}
+
+void
+rb_iseq_pc2branchindex_set(rb_iseq_t *iseq, VALUE h)
+{
+ if (!RTEST(h) && !ISEQ_VARIABLE(iseq)) return;
+ struct rb_iseq_variable *v = rb_iseq_variable_ensure(iseq);
+ RB_OBJ_WRITE(iseq, &v->pc2branchindex, h);
+}
+
+rb_snum_t
+rb_iseq_flip_cnt_increment(const rb_iseq_t *iseq)
+{
+ struct rb_iseq_variable *v = rb_iseq_variable_ensure((rb_iseq_t *)iseq);
+ rb_snum_t cnt = v->flip_count;
+ v->flip_count += 1;
+ return cnt;
+}
+
typedef VALUE iseq_value_itr_t(void *ctx, VALUE obj);
static inline void
@@ -376,7 +425,8 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating)
rb_iseq_mark_and_move_each_body_value(iseq, reference_updating ? ISEQ_ORIGINAL_ISEQ(iseq) : NULL);
- rb_gc_mark_and_move(&body->variable.script_lines);
+ struct rb_iseq_variable *v = ISEQ_VARIABLE(iseq);
+ if (v) rb_gc_mark_and_move(&v->script_lines);
rb_gc_mark_and_move(&body->location.label);
rb_gc_mark_and_move(&body->location.base_label);
rb_gc_mark_and_move(&body->location.pathobj);
@@ -488,8 +538,10 @@ rb_iseq_mark_and_move(rb_iseq_t *iseq, bool reference_updating)
// TODO: ractor aware coverage
if (!rb_gc_checking_shareable()) {
- rb_gc_mark_and_move(&body->variable.coverage);
- rb_gc_mark_and_move(&body->variable.pc2branchindex);
+ if (v) {
+ rb_gc_mark_and_move(&v->coverage);
+ rb_gc_mark_and_move(&v->pc2branchindex);
+ }
}
}
@@ -541,6 +593,7 @@ rb_iseq_memsize(const rb_iseq_t *iseq)
if (ISEQ_EXECUTABLE_P(iseq) && body) {
size += sizeof(struct rb_iseq_constant_body);
+ if (body->variable) size += sizeof(struct rb_iseq_variable);
size += body->iseq_size * sizeof(VALUE);
size += body->insns_info.size * (sizeof(struct iseq_insn_info_entry) + sizeof(unsigned int));
size += body->local_table_size * sizeof(ID); // body->local_table
@@ -758,15 +811,16 @@ prepare_iseq_build(rb_iseq_t *iseq,
if (iseq != body->local_iseq) {
RB_OBJ_WRITE(iseq, &body->location.base_label, ISEQ_BODY(body->local_iseq)->location.label);
}
- ISEQ_COVERAGE_SET(iseq, Qnil);
ISEQ_ORIGINAL_ISEQ_CLEAR(iseq);
- body->variable.flip_count = 0;
-
- if (NIL_P(script_lines)) {
- RB_OBJ_WRITE(iseq, &body->variable.script_lines, Qnil);
+ if (body->variable) {
+ body->variable->flip_count = 0;
+ RB_OBJ_WRITE(iseq, &body->variable->script_lines, Qnil);
+ RB_OBJ_WRITE(iseq, &body->variable->coverage, Qnil);
}
- else {
- RB_OBJ_WRITE(iseq, &body->variable.script_lines, rb_ractor_make_shareable(script_lines));
+
+ if (!NIL_P(script_lines)) {
+ struct rb_iseq_variable *v = rb_iseq_variable_ensure(iseq);
+ RB_OBJ_WRITE(iseq, &v->script_lines, rb_ractor_make_shareable(script_lines));
}
ISEQ_COMPILE_DATA_ALLOC(iseq);
@@ -806,13 +860,10 @@ rb_iseq_insns_info_encode_positions(const rb_iseq_t *iseq)
struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq);
int size = body->insns_info.size;
int max_pos = body->iseq_size;
- int *data = (int *)body->insns_info.positions;
- if (body->insns_info.succ_index_table) ruby_xfree(body->insns_info.succ_index_table);
- body->insns_info.succ_index_table = succ_index_table_create(max_pos, data, size);
-#if VM_CHECK_MODE == 0
- SIZED_FREE_N(body->insns_info.positions, body->insns_info.size);
- body->insns_info.positions = NULL;
-#endif
+ unsigned int *positions = body->insns_info.positions_or_succ_index_table.positions;
+ struct succ_index_table *sd = succ_index_table_create(max_pos, (int *)positions, size);
+ SIZED_FREE_N(positions, size);
+ body->insns_info.positions_or_succ_index_table.succ_index_table = sd;
#endif
}
@@ -822,7 +873,7 @@ rb_iseq_insns_info_decode_positions(const struct rb_iseq_constant_body *body)
{
int size = body->insns_info.size;
int max_pos = body->iseq_size;
- struct succ_index_table *sd = body->insns_info.succ_index_table;
+ struct succ_index_table *sd = body->insns_info.positions_or_succ_index_table.succ_index_table;
return succ_index_table_invert(max_pos, sd, size);
}
#endif
@@ -1132,7 +1183,7 @@ rb_iseq_new_with_opt(VALUE ast_value, VALUE name, VALUE path, VALUE realpath,
script_lines = rb_parser_build_script_lines_from(body->script_lines);
}
else if (parent) {
- script_lines = ISEQ_BODY(parent)->variable.script_lines;
+ script_lines = ISEQ_SCRIPT_LINES(parent);
}
prepare_iseq_build(iseq, name, path, realpath, first_lineno, node ? &node->nd_loc : NULL, prepare_node_id(node),
@@ -1629,7 +1680,9 @@ remove_coverage_i(void *vstart, void *vend, size_t stride, void *data)
if (rb_obj_is_iseq(v)) {
rb_iseq_t *iseq = (rb_iseq_t *)v;
- ISEQ_COVERAGE_SET(iseq, Qnil);
+ if (ISEQ_VARIABLE(iseq)) {
+ ISEQ_COVERAGE_SET(iseq, Qnil);
+ }
}
asan_poison_object_if(ptr, v);
@@ -2364,7 +2417,7 @@ get_insn_info_binary_search(const rb_iseq_t *iseq, size_t pos)
const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq);
size_t size = body->insns_info.size;
const struct iseq_insn_info_entry *insns_info = body->insns_info.body;
- const unsigned int *positions = body->insns_info.positions;
+ const unsigned int *positions = body->insns_info.positions_or_succ_index_table.positions;
const int debug = 0;
if (debug) {
@@ -2420,16 +2473,9 @@ get_insn_info_succinct_bitvector(const rb_iseq_t *iseq, size_t pos)
const int debug = 0;
if (debug) {
-#if VM_CHECK_MODE > 0
- const unsigned int *positions = body->insns_info.positions;
- printf("size: %"PRIuSIZE"\n", size);
- printf("insns_info[%"PRIuSIZE"]: position: %d, line: %d, pos: %"PRIuSIZE"\n",
- (size_t)0, positions[0], insns_info[0].line_no, pos);
-#else
printf("size: %"PRIuSIZE"\n", size);
printf("insns_info[%"PRIuSIZE"]: line: %d, pos: %"PRIuSIZE"\n",
(size_t)0, insns_info[0].line_no, pos);
-#endif
}
if (size == 0) {
@@ -2440,8 +2486,8 @@ get_insn_info_succinct_bitvector(const rb_iseq_t *iseq, size_t pos)
}
else {
int index;
- VM_ASSERT(body->insns_info.succ_index_table != NULL);
- index = succ_index_lookup(body->insns_info.succ_index_table, (int)pos);
+ VM_ASSERT(body->insns_info.positions_or_succ_index_table.succ_index_table != NULL);
+ index = succ_index_lookup(body->insns_info.positions_or_succ_index_table.succ_index_table, (int)pos);
return &insns_info[index-1];
}
}
@@ -2455,12 +2501,11 @@ get_insn_info(const rb_iseq_t *iseq, size_t pos)
#if VM_CHECK_MODE > 0 || VM_INSN_INFO_TABLE_IMPL == 0
static const struct iseq_insn_info_entry *
-get_insn_info_linear_search(const rb_iseq_t *iseq, size_t pos)
+get_insn_info_linear_search(const rb_iseq_t *iseq, const unsigned int *positions, size_t pos)
{
const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq);
size_t i = 0, size = body->insns_info.size;
const struct iseq_insn_info_entry *insns_info = body->insns_info.body;
- const unsigned int *positions = body->insns_info.positions;
const int debug = 0;
if (debug) {
@@ -2496,7 +2541,7 @@ get_insn_info_linear_search(const rb_iseq_t *iseq, size_t pos)
static const struct iseq_insn_info_entry *
get_insn_info(const rb_iseq_t *iseq, size_t pos)
{
- return get_insn_info_linear_search(iseq, pos);
+ return get_insn_info_linear_search(iseq, ISEQ_BODY(iseq)->insns_info.positions_or_succ_index_table.positions, pos);
}
#endif
@@ -2506,11 +2551,19 @@ validate_get_insn_info(const rb_iseq_t *iseq)
{
const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq);
size_t i;
+#if VM_INSN_INFO_TABLE_IMPL == 2
+ unsigned int *positions = rb_iseq_insns_info_decode_positions(body);
+#else
+ const unsigned int *positions = body->insns_info.positions_or_succ_index_table.positions;
+#endif
for (i = 0; i < body->iseq_size; i++) {
- if (get_insn_info_linear_search(iseq, i) != get_insn_info(iseq, i)) {
+ if (get_insn_info_linear_search(iseq, positions, i) != get_insn_info(iseq, i)) {
rb_bug("validate_get_insn_info: get_insn_info_linear_search(iseq, %"PRIuSIZE") != get_insn_info(iseq, %"PRIuSIZE")", i, i);
}
}
+#if VM_INSN_INFO_TABLE_IMPL == 2
+ SIZED_FREE_N(positions, body->insns_info.size);
+#endif
}
#endif
@@ -4598,7 +4651,7 @@ static VALUE
iseqw_script_lines(VALUE self)
{
const rb_iseq_t *iseq = iseqw_check(self);
- return ISEQ_BODY(iseq)->variable.script_lines;
+ return ISEQ_SCRIPT_LINES(iseq);
}
/* Returns the hash of the source this iseq was compiled from, or nil if it
diff --git a/iseq.h b/iseq.h
index e641dd6aed1f5e..e75e16788f4ab3 100644
--- a/iseq.h
+++ b/iseq.h
@@ -58,41 +58,85 @@ typedef void (*rb_iseq_callback)(const rb_iseq_t *, void *);
extern const ID rb_iseq_shared_exc_local_tbl[];
-#define ISEQ_COVERAGE(iseq) ISEQ_BODY(iseq)->variable.coverage
-#define ISEQ_COVERAGE_SET(iseq, cov) RB_OBJ_WRITE(iseq, &ISEQ_BODY(iseq)->variable.coverage, cov)
-#define ISEQ_LINE_COVERAGE(iseq) RARRAY_AREF(ISEQ_COVERAGE(iseq), COVERAGE_INDEX_LINES)
-#define ISEQ_BRANCH_COVERAGE(iseq) RARRAY_AREF(ISEQ_COVERAGE(iseq), COVERAGE_INDEX_BRANCHES)
+/* Ensure body->variable is allocated, returning the struct. */
+struct rb_iseq_variable *rb_iseq_variable_ensure(rb_iseq_t *iseq);
+
+static inline struct rb_iseq_variable *
+ISEQ_BODY_VARIABLE(const rb_iseq_t *iseq)
+{
+ return ISEQ_BODY(iseq)->variable;
+}
-#define ISEQ_PC2BRANCHINDEX(iseq) ISEQ_BODY(iseq)->variable.pc2branchindex
-#define ISEQ_PC2BRANCHINDEX_SET(iseq, h) RB_OBJ_WRITE(iseq, &ISEQ_BODY(iseq)->variable.pc2branchindex, h)
+/* NULL-safe read accessors for body->variable fields. */
+static inline VALUE
+ISEQ_BODY_VARIABLE_SCRIPT_LINES(const rb_iseq_t *iseq)
+{
+ struct rb_iseq_variable *v = ISEQ_BODY_VARIABLE(iseq);
+ return v ? v->script_lines : Qnil;
+}
-#define ISEQ_FLIP_CNT(iseq) ISEQ_BODY(iseq)->variable.flip_count
+static inline VALUE
+ISEQ_BODY_VARIABLE_COVERAGE(const rb_iseq_t *iseq)
+{
+ struct rb_iseq_variable *v = ISEQ_BODY_VARIABLE(iseq);
+ return v ? v->coverage : Qfalse;
+}
-#define ISEQ_FROZEN_STRING_LITERAL_ENABLED 1
-#define ISEQ_FROZEN_STRING_LITERAL_DISABLED 0
-#define ISEQ_FROZEN_STRING_LITERAL_UNSET -1
+static inline VALUE
+ISEQ_BODY_VARIABLE_PC2BRANCHINDEX(const rb_iseq_t *iseq)
+{
+ struct rb_iseq_variable *v = ISEQ_BODY_VARIABLE(iseq);
+ return v ? v->pc2branchindex : Qfalse;
+}
static inline rb_snum_t
-ISEQ_FLIP_CNT_INCREMENT(const rb_iseq_t *iseq)
+ISEQ_BODY_VARIABLE_FLIP_CNT(const rb_iseq_t *iseq)
{
- rb_snum_t cnt = ISEQ_BODY(iseq)->variable.flip_count;
- ISEQ_BODY(iseq)->variable.flip_count += 1;
- return cnt;
+ struct rb_iseq_variable *v = ISEQ_BODY_VARIABLE(iseq);
+ return v ? v->flip_count : 0;
}
static inline VALUE *
-ISEQ_ORIGINAL_ISEQ(const rb_iseq_t *iseq)
+ISEQ_BODY_VARIABLE_ORIGINAL_ISEQ(const rb_iseq_t *iseq)
{
- return ISEQ_BODY(iseq)->variable.original_iseq;
+ struct rb_iseq_variable *v = ISEQ_BODY_VARIABLE(iseq);
+ return v ? v->original_iseq : NULL;
}
+/* Write accessors (lazily allocate variable as needed). */
+void rb_iseq_coverage_set(rb_iseq_t *iseq, VALUE cov);
+void rb_iseq_pc2branchindex_set(rb_iseq_t *iseq, VALUE h);
+rb_snum_t rb_iseq_flip_cnt_increment(const rb_iseq_t *iseq);
+
+/* Short macros for reading variable fields. */
+#define ISEQ_VARIABLE(iseq) ISEQ_BODY_VARIABLE(iseq)
+#define ISEQ_SCRIPT_LINES(iseq) ISEQ_BODY_VARIABLE_SCRIPT_LINES(iseq)
+#define ISEQ_COVERAGE(iseq) ISEQ_BODY_VARIABLE_COVERAGE(iseq)
+#define ISEQ_COVERAGE_SET(iseq, cov) rb_iseq_coverage_set(iseq, cov)
+#define ISEQ_LINE_COVERAGE(iseq) RARRAY_AREF(ISEQ_COVERAGE(iseq), COVERAGE_INDEX_LINES)
+#define ISEQ_BRANCH_COVERAGE(iseq) RARRAY_AREF(ISEQ_COVERAGE(iseq), COVERAGE_INDEX_BRANCHES)
+
+#define ISEQ_PC2BRANCHINDEX(iseq) ISEQ_BODY_VARIABLE_PC2BRANCHINDEX(iseq)
+#define ISEQ_PC2BRANCHINDEX_SET(iseq,h) rb_iseq_pc2branchindex_set(iseq, h)
+
+#define ISEQ_FLIP_CNT(iseq) ISEQ_BODY_VARIABLE_FLIP_CNT(iseq)
+#define ISEQ_FLIP_CNT_INCREMENT(iseq) rb_iseq_flip_cnt_increment(iseq)
+#define ISEQ_ORIGINAL_ISEQ(iseq) ISEQ_BODY_VARIABLE_ORIGINAL_ISEQ(iseq)
+
+#define ISEQ_FROZEN_STRING_LITERAL_ENABLED 1
+#define ISEQ_FROZEN_STRING_LITERAL_DISABLED 0
+#define ISEQ_FROZEN_STRING_LITERAL_UNSET -1
+
static inline void
ISEQ_ORIGINAL_ISEQ_CLEAR(const rb_iseq_t *iseq)
{
- VALUE *ptr = (VALUE *)ISEQ_BODY(iseq)->variable.original_iseq;
- if (ptr) {
- ISEQ_BODY(iseq)->variable.original_iseq = NULL;
- SIZED_FREE_N(ptr, ISEQ_BODY(iseq)->iseq_size);
+ struct rb_iseq_variable *v = ISEQ_BODY_VARIABLE(iseq);
+ if (v) {
+ VALUE *ptr = v->original_iseq;
+ if (ptr) {
+ v->original_iseq = NULL;
+ SIZED_FREE_N(ptr, ISEQ_BODY(iseq)->iseq_size);
+ }
}
}
diff --git a/spec/ruby/core/io/buffer/map_spec.rb b/spec/ruby/core/io/buffer/map_spec.rb
index a711271fd54599..5781c731fbc75b 100644
--- a/spec/ruby/core/io/buffer/map_spec.rb
+++ b/spec/ruby/core/io/buffer/map_spec.rb
@@ -31,6 +31,15 @@ def open_big_file_fixture
File.open(@big_file_name, "rb+")
end
+ def open_map_aligned_file_fixture
+ unless @map_aligned_file_name
+ @map_aligned_file_name = tmp("map_aligned_file")
+ File.write(@map_aligned_file_name, "12345678" * (IO::Buffer::MAP_ALIGNMENT / 8 + 2))
+ @tmp_files << @map_aligned_file_name
+ end
+ File.open(@map_aligned_file_name, "rb+")
+ end
+
after :each do
@buffer&.free
@buffer = nil
@@ -179,8 +188,32 @@ def open_big_file_fixture
end
context "with size and offset arguments" do
- # Neither Windows nor macOS have clear, stable behavior with non-zero offset.
- # https://bugs.ruby-lang.org/issues/21700
+ ruby_version_is "4.1" do
+ it "maps a file from an offset aligned to MAP_ALIGNMENT" do
+ @file = open_map_aligned_file_fixture
+ @buffer = IO::Buffer.map(@file, 14, IO::Buffer::MAP_ALIGNMENT)
+
+ @buffer.size.should == 14
+ @buffer.get_string(0, 14).should == "12345678123456"
+ end
+
+ it "maps the rest of a file from an offset aligned to MAP_ALIGNMENT" do
+ @file = open_map_aligned_file_fixture
+ @buffer = IO::Buffer.map(@file, nil, IO::Buffer::MAP_ALIGNMENT)
+
+ @buffer.get_string(0, 1).should == "1"
+ @buffer.size.should == (@file.size - IO::Buffer::MAP_ALIGNMENT)
+ end
+
+ it "raises ArgumentError if offset is not aligned to MAP_ALIGNMENT" do
+ @file = open_fixture
+ message = "Offset (1) must be a multiple of IO::Buffer::MAP_ALIGNMENT (#{IO::Buffer::MAP_ALIGNMENT})!"
+
+ -> { IO::Buffer.map(@file, 1, 1) }.should.raise(ArgumentError, message)
+ end
+ end
+
+ # Before MAP_ALIGNMENT was exposed, these offsets were only portable to Linux.
platform_is :linux do
context "if offset is an allowed value for system call" do
it "maps the span specified by size starting from the offset" do
@@ -252,10 +285,10 @@ def open_big_file_fixture
ruby_version_is "4.1" do
it "raises ArgumentError if offset+size is larger than file size" do
- @file = open_big_file_fixture
+ @file = open_map_aligned_file_fixture
size = 17
- maximum_page_size = 0
- -> { IO::Buffer.map(@file, size, IO::Buffer::PAGE_SIZE) }.should.raise(ArgumentError, "Offset (#{IO::Buffer::PAGE_SIZE}) can't be larger than #{maximum_page_size} for requested size (#{size})")
+ maximum_offset = 0
+ -> { IO::Buffer.map(@file, size, IO::Buffer::MAP_ALIGNMENT) }.should.raise(ArgumentError, "Offset (#{IO::Buffer::MAP_ALIGNMENT}) can't be larger than #{maximum_offset} for requested size (#{size})")
ensure
# Windows requires the file to be closed before deletion.
@file.close unless @file.closed?
diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb
index e7940faa4ee6f4..84839585fcc755 100644
--- a/test/json/json_parser_test.rb
+++ b/test/json/json_parser_test.rb
@@ -847,6 +847,80 @@ def test_parse_error_snippet
assert_equal "unexpected character: '@' at line 1 column 1", error.message
end
+ def test_parse_error_json_path
+ omit "JRuby errors don't contain positions" if RUBY_ENGINE == "jruby"
+
+ assert_parse_error_at "$", "xyz"
+ assert_parse_error_at "$.a", '{"a": xyz}'
+ assert_parse_error_at "$[3]", '[1, 2, "hi", xyz]'
+ assert_parse_error_at "$.a[1].b", '{"a": [1, {"b": xyz}]}'
+ assert_parse_error_at "$.a", '{"a": 1 xyz}'
+ assert_parse_error_at "$", '{"a": 1, xyz}'
+
+ assert_parse_error_at "$.a.b.c", '{"a": {"b": {"c"'
+ assert_parse_error_at "$.a.b.c", '{"a": {"b": {"c":'
+ assert_parse_error_at "$.a.b", '{"a": {"b": {"c": 1, "d'
+
+ assert_parse_error_at "$[4]", '[1,2,3,4,5'
+ assert_parse_error_at "$[5]", '[1,2,3,4,5,'
+ assert_parse_error_at "$[5]", '[1,2,3,4,5,]'
+ end
+
+ def test_parse_error_json_path_on_load
+ omit "JRuby errors don't contain positions" if RUBY_ENGINE == "jruby"
+
+ assert_parse_error_at "$" do
+ JSON.load('{"a": {"b": {"c":', -> (obj) {
+ if String === obj
+ BasicObject.new
+ else
+ obj
+ end
+ })
+ end
+
+ assert_parse_error_at "$.a" do
+ JSON.load('{"a": {"b": {"c":', -> (obj) {
+ if obj == "b"
+ BasicObject.new
+ else
+ obj
+ end
+ })
+ end
+ end
+
+ def test_parse_error_json_path_key_escaping
+ omit "JRuby errors don't contain positions" if RUBY_ENGINE == "jruby"
+
+ assert_parse_error_at '$["hello world"]', '{"hello world": xyz}'
+ assert_parse_error_at '$["a\"b"]', '{"a\"b": xyz}'
+ assert_parse_error_at '$[""]', '{"": xyz}'
+ assert_parse_error_at '$["あ"]', '{"あ": xyz}'
+ assert_parse_error_at '$.foo["1x"]', '{"foo": {"1x": xyz}}'
+ end
+
+ def test_parse_error_json_path_duplicate_key
+ omit "JRuby errors don't contain positions" if RUBY_ENGINE == "jruby"
+
+ assert_parse_error_at "$.a", '{"a": 1, "a": 2}'
+ assert_parse_error_at "$.x.a", '{"x": {"a": 1, "b": 2, "a": 3}}'
+ assert_parse_error_at "$.arr[0].a", '{"arr": [{"a": 1, "a": 2}]}'
+ assert_parse_error_at "$.x.a", '{"x": {"a": 1, "a": 2}}'
+ end
+
+ def test_parse_error_json_path_resumable
+ omit "JSON::ResumableParser not available" unless defined?(JSON::ResumableParser)
+
+ parser = JSON::ResumableParser.new
+ parser << '{"a": [1, {"b": '
+ parser.parse
+ assert_parse_error_at "$.a[1].b" do
+ parser << 'xyz'
+ parser.parse
+ end
+ end
+
def test_parse_leading_slash
# ref: https://github.com/ruby/ruby/pull/12598
assert_raise(JSON::ParserError) do
@@ -888,4 +962,15 @@ def assert_equal_float(expected, actual, delta = 1e-2)
Array === actual and actual = actual.first
assert_in_delta(expected, actual, delta)
end
+
+ def assert_parse_error_at(path, json = nil)
+ error = assert_raise(JSON::ParserError) do
+ if block_given?
+ yield
+ else
+ JSON.parse(json)
+ end
+ end
+ assert_equal path, error.json_path
+ end
end
diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb
index 1e6baac2710242..500ca5b7eb0b56 100644
--- a/test/ruby/test_io_buffer.rb
+++ b/test/ruby/test_io_buffer.rb
@@ -32,6 +32,11 @@ def test_flags
assert_equal 128, IO::Buffer::READONLY
end
+ def test_map_alignment
+ assert_kind_of Integer, IO::Buffer::MAP_ALIGNMENT
+ assert_positive IO::Buffer::MAP_ALIGNMENT
+ end
+
def test_internal_for_reading_with_string
string = "hello"
@@ -211,6 +216,33 @@ def test_file_mapped_with_size
assert_equal Encoding::BINARY, contents.encoding
end
+ def test_file_mapped_with_aligned_offset
+ alignment = IO::Buffer::MAP_ALIGNMENT
+
+ Tempfile.create do |file|
+ file.binmode
+ file.write("\0" * alignment)
+ file.write("test")
+ file.flush
+
+ buffer = IO::Buffer.map(file, 4, alignment, IO::Buffer::READONLY)
+ assert_equal "test", buffer.get_string
+ ensure
+ buffer&.free
+ end
+ end
+
+ def test_file_mapped_with_unaligned_offset
+ alignment = IO::Buffer::MAP_ALIGNMENT
+ message = "Offset (1) must be a multiple of IO::Buffer::MAP_ALIGNMENT (#{alignment})!"
+
+ File.open(__FILE__) do |file|
+ assert_raise_with_message(ArgumentError, message) do
+ IO::Buffer.map(file, 1, 1, IO::Buffer::READONLY)
+ end
+ end
+ end
+
def test_file_mapped_size_too_large
size = File.size(__FILE__) + 1
file_size = File.size(__FILE__)
@@ -236,19 +268,19 @@ def test_file_mapped_offset_negative
def test_file_mapped_offset_too_large
file_size = File.size(__FILE__)
- page_count = file_size / IO::Buffer::PAGE_SIZE
- offset = IO::Buffer::PAGE_SIZE * (page_count + 1)
+ alignment_count = file_size / IO::Buffer::MAP_ALIGNMENT
+ offset = IO::Buffer::MAP_ALIGNMENT * (alignment_count + 1)
message = "Offset (#{offset}) can't be larger than file size (#{file_size})"
assert_raise_with_message ArgumentError, message do
File.open(__FILE__) {|file| IO::Buffer.map(file, nil, offset, IO::Buffer::READONLY)}
end
- if page_count > 0
- offset = IO::Buffer::PAGE_SIZE * page_count
+ if alignment_count > 0
+ offset = IO::Buffer::MAP_ALIGNMENT * alignment_count
available_size = file_size - offset
size = available_size + 1
- maximum_page_count = (file_size - size) / IO::Buffer::PAGE_SIZE
- maximum_offset = IO::Buffer::PAGE_SIZE * maximum_page_count
+ maximum_alignment_count = (file_size - size) / IO::Buffer::MAP_ALIGNMENT
+ maximum_offset = IO::Buffer::MAP_ALIGNMENT * maximum_alignment_count
message = "Offset (#{offset}) can't be larger than #{maximum_offset} " +
"for requested size (#{size})"
assert_raise_with_message ArgumentError, message do
diff --git a/test/ruby/test_yjit.rb b/test/ruby/test_yjit.rb
index 43aae5fa46a695..0ed394fc8ecef5 100644
--- a/test/ruby/test_yjit.rb
+++ b/test/ruby/test_yjit.rb
@@ -1223,6 +1223,26 @@ def case_dispatch(val)
RUBY
end
+ def test_opt_case_dispatch_all_byte_values
+ branches = 256.times.map { |value| "when #{value} then #{value}" }.join("\n")
+ assert_compiles(<<~RUBY, exits: :any, result: :ok)
+ def case_dispatch(value)
+ case value
+ #{branches}
+ end
+ end
+
+ values = (0...256).to_a
+ return :wrong_result unless values.map { |value| case_dispatch(value) } == values
+
+ stats = RubyVM::YJIT.runtime_stats
+ return :early_fallback if stats[:all_stats] && !stats[:num_opt_case_dispatch_megamorphic].zero?
+ return :wrong_else unless 2.times.map { case_dispatch(256) } == [nil, nil]
+ return :ok unless stats[:all_stats]
+ RubyVM::YJIT.runtime_stats[:num_opt_case_dispatch_megamorphic].positive? ? :ok : :missing_fallback
+ RUBY
+ end
+
def test_code_gc
assert_compiles(code_gc_helpers + <<~'RUBY', exits: :any, result: :ok)
return :not_paged unless add_pages(100) # prepare freeable pages
diff --git a/vm_backtrace.c b/vm_backtrace.c
index 54845124f100d8..f13d4273179785 100644
--- a/vm_backtrace.c
+++ b/vm_backtrace.c
@@ -560,7 +560,7 @@ location_source_first_lineno(const rb_iseq_t *iseq, VALUE script_lines)
while (ISEQ_BODY(source_iseq)->parent_iseq) {
const rb_iseq_t *parent = ISEQ_BODY(source_iseq)->parent_iseq;
- if (ISEQ_BODY(parent)->variable.script_lines != script_lines) break;
+ if (ISEQ_SCRIPT_LINES(parent) != script_lines) break;
source_iseq = parent;
}
@@ -608,7 +608,7 @@ location_source_range_m(VALUE self)
VALUE path = rb_iseq_path(iseq);
VALUE absolute_path = rb_iseq_realpath(iseq);
- VALUE script_lines = ISEQ_BODY(iseq)->variable.script_lines;
+ VALUE script_lines = ISEQ_SCRIPT_LINES(iseq);
VALUE source;
VALUE parser_path = path;
int first_lineno = 1;
diff --git a/vm_core.h b/vm_core.h
index 3dbfe7442a7f1d..5f316b90e147ed 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -416,6 +416,16 @@ enum lvar_state {
lvar_reassigned,
};
+/* Lazily-allocated per-iseq variable data. NULL when unused (the common case:
+ * no coverage, no script_lines, no flip-flops, no disassembly). */
+struct rb_iseq_variable {
+ rb_snum_t flip_count;
+ VALUE script_lines;
+ VALUE coverage;
+ VALUE pc2branchindex;
+ VALUE *original_iseq;
+};
+
struct rb_iseq_constant_body {
enum rb_iseq_type type;
@@ -504,11 +514,13 @@ struct rb_iseq_constant_body {
/* insn info, must be freed */
struct iseq_insn_info {
const struct iseq_insn_info_entry *body;
- unsigned int *positions;
- unsigned int size;
+ union {
+ unsigned int *positions;
#if VM_INSN_INFO_TABLE_IMPL == 2
- struct succ_index_table *succ_index_table;
+ struct succ_index_table *succ_index_table;
#endif
+ } positions_or_succ_index_table;
+ unsigned int size;
} insns_info;
const ID *local_table; /* must free */
@@ -525,13 +537,7 @@ struct rb_iseq_constant_body {
union iseq_inline_storage_entry *is_entries; /* [ TS_IVC | TS_ICVARC | TS_ISE | TS_IC ] */
struct rb_call_data *call_data; //struct rb_call_data calls[ci_size];
- struct {
- rb_snum_t flip_count;
- VALUE script_lines;
- VALUE coverage;
- VALUE pc2branchindex;
- VALUE *original_iseq;
- } variable;
+ struct rb_iseq_variable *variable;
unsigned int local_table_size;
unsigned int ic_size; // Number of IC caches
diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs
index 9483b715a69671..dffb4593c09202 100644
--- a/yjit/src/codegen.rs
+++ b/yjit/src/codegen.rs
@@ -2875,7 +2875,7 @@ fn jit_chain_guard(
jcc: JCCKinds,
jit: &mut JITState,
asm: &mut Assembler,
- depth_limit: u8,
+ depth_limit: u16,
counter: Counter,
) {
let target0_gen_fn = match jcc {
@@ -2902,22 +2902,22 @@ fn jit_chain_guard(
}
// up to 8 different shapes for each
-pub const GET_IVAR_MAX_DEPTH: u8 = 8;
+pub const GET_IVAR_MAX_DEPTH: u16 = 8;
// up to 8 different shapes for each
-pub const SET_IVAR_MAX_DEPTH: u8 = 8;
+pub const SET_IVAR_MAX_DEPTH: u16 = 8;
// hashes and arrays
-pub const OPT_AREF_MAX_CHAIN_DEPTH: u8 = 2;
+pub const OPT_AREF_MAX_CHAIN_DEPTH: u16 = 2;
// expandarray
-pub const EXPANDARRAY_MAX_CHAIN_DEPTH: u8 = 4;
+pub const EXPANDARRAY_MAX_CHAIN_DEPTH: u16 = 4;
// up to 5 different methods for send
-pub const SEND_MAX_DEPTH: u8 = 5;
+pub const SEND_MAX_DEPTH: u16 = 5;
-// up to 20 different offsets for case-when
-pub const CASE_WHEN_MAX_DEPTH: u8 = 20;
+// Specialize every value of a byte-sized case expression
+pub const CASE_WHEN_MAX_DEPTH: u16 = 256;
pub const MAX_SPLAT_LENGTH: i32 = 127;
@@ -2928,7 +2928,7 @@ pub const MAX_SPLAT_LENGTH: i32 = 127;
fn gen_get_ivar(
jit: &mut JITState,
asm: &mut Assembler,
- max_chain_depth: u8,
+ max_chain_depth: u16,
comptime_receiver: VALUE,
ivar_name: ID,
recv: Opnd,
@@ -4982,7 +4982,7 @@ fn jit_guard_known_klass(
obj_opnd: Opnd,
insn_opnd: YARVOpnd,
sample_instance: VALUE,
- max_chain_depth: u8,
+ max_chain_depth: u16,
counter: Counter,
) {
let known_klass = sample_instance.class_of();
diff --git a/yjit/src/core.rs b/yjit/src/core.rs
index d08cd1fb26fac3..bdea7ccceeb8f3 100644
--- a/yjit/src/core.rs
+++ b/yjit/src/core.rs
@@ -460,8 +460,10 @@ impl fmt::Debug for RegMapping {
}
}
-/// Maximum value of the chain depth (should fit in 5 bits)
-const CHAIN_DEPTH_MAX: u8 = 0b11111; // 31
+/// Maximum value of the chain depth (should fit in 9 bits)
+const CHAIN_DEPTH_MAX: u16 = 256;
+const DEFERRED_FLAG: u8 = 1 << 0;
+const RETURN_LANDING_FLAG: u8 = 1 << 1;
/// Code generation context
/// Contains information we can use to specialize/optimize code
@@ -478,14 +480,11 @@ pub struct Context {
reg_mapping: RegMapping,
// Depth of this block in the sidechain (eg: inline-cache chain)
- // 6 bits, max 63
- chain_depth: u8,
+ // 9 bits, max 256
+ chain_depth: u16,
- // Whether this code is the target of a JIT-to-JIT Ruby return ([Self::is_return_landing])
- is_return_landing: bool,
-
- // Whether the compilation of this code has been deferred ([Self::is_deferred])
- is_deferred: bool,
+ // Return-landing and deferred flags
+ flags: u8,
// Type we track for self
self_type: Type,
@@ -580,9 +579,9 @@ impl BitVector {
self.push_uint(val as u64, 8);
}
- fn push_u5(&mut self, val: u8) {
- assert!(val <= 0b11111);
- self.push_uint(val as u64, 5);
+ fn push_u9(&mut self, val: u16) {
+ assert!(val <= 0b1_1111_1111);
+ self.push_uint(val as u64, 9);
}
fn push_u4(&mut self, val: u8) {
@@ -654,8 +653,8 @@ impl BitVector {
self.read_uint(bit_idx, 8) as u8
}
- fn read_u5(&self, bit_idx: &mut usize) -> u8 {
- self.read_uint(bit_idx, 5) as u8
+ fn read_u9(&self, bit_idx: &mut usize) -> u16 {
+ self.read_uint(bit_idx, 9) as u16
}
fn read_u4(&self, bit_idx: &mut usize) -> u8 {
@@ -1053,17 +1052,17 @@ impl Context {
}
}
- bits.push_bool(self.is_deferred);
- bits.push_bool(self.is_return_landing);
+ bits.push_bool(self.is_deferred());
+ bits.push_bool(self.is_return_landing());
// The chain depth is most often 0 or 1
if self.chain_depth < 2 {
bits.push_u1(0);
- bits.push_u1(self.chain_depth);
+ bits.push_u1(self.chain_depth.try_into().unwrap());
} else {
bits.push_u1(1);
- bits.push_u5(self.chain_depth);
+ bits.push_u9(self.chain_depth);
}
// Encode the self type if known
@@ -1154,13 +1153,17 @@ impl Context {
}
}
- ctx.is_deferred = bits.read_bool(&mut idx);
- ctx.is_return_landing = bits.read_bool(&mut idx);
+ if bits.read_bool(&mut idx) {
+ ctx.flags |= DEFERRED_FLAG;
+ }
+ if bits.read_bool(&mut idx) {
+ ctx.flags |= RETURN_LANDING_FLAG;
+ }
if bits.read_u1(&mut idx) == 0 {
- ctx.chain_depth = bits.read_u1(&mut idx)
+ ctx.chain_depth = bits.read_u1(&mut idx).into()
} else {
- ctx.chain_depth = bits.read_u5(&mut idx)
+ ctx.chain_depth = bits.read_u9(&mut idx);
}
loop {
@@ -2577,13 +2580,13 @@ impl Context {
self.reg_mapping = reg_mapping;
}
- pub fn get_chain_depth(&self) -> u8 {
+ pub fn get_chain_depth(&self) -> u16 {
self.chain_depth
}
pub fn reset_chain_depth_and_defer(&mut self) {
self.chain_depth = 0;
- self.is_deferred = false;
+ self.flags &= !DEFERRED_FLAG;
}
pub fn increment_chain_depth(&mut self) {
@@ -2594,23 +2597,23 @@ impl Context {
}
pub fn set_as_return_landing(&mut self) {
- self.is_return_landing = true;
+ self.flags |= RETURN_LANDING_FLAG;
}
pub fn clear_return_landing(&mut self) {
- self.is_return_landing = false;
+ self.flags &= !RETURN_LANDING_FLAG;
}
pub fn is_return_landing(&self) -> bool {
- self.is_return_landing
+ self.flags & RETURN_LANDING_FLAG != 0
}
pub fn mark_as_deferred(&mut self) {
- self.is_deferred = true;
+ self.flags |= DEFERRED_FLAG;
}
pub fn is_deferred(&self) -> bool {
- self.is_deferred
+ self.flags & DEFERRED_FLAG != 0
}
/// Get an operand for the adjusted stack pointer address
@@ -4373,6 +4376,7 @@ mod tests {
// Check that we can store types in 4 bits,
// and all local types in 32 bits
assert_eq!(mem::size_of::(), 1);
+ assert_eq!(mem::size_of::(), 56);
assert!(Type::BlockParamProxy as usize <= 0b1111);
assert!(MAX_CTX_LOCALS * 4 <= 32);
}
diff --git a/yjit/src/log.rs b/yjit/src/log.rs
index c5a724f7e1df52..223b76176e2aae 100644
--- a/yjit/src/log.rs
+++ b/yjit/src/log.rs
@@ -46,7 +46,7 @@ impl Log {
}
}
- pub fn add_block_with_chain_depth(block_id: BlockId, chain_depth: u8) {
+ pub fn add_block_with_chain_depth(block_id: BlockId, chain_depth: u16) {
if !Self::has_instance() {
return;
}
diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs
index 7c91d91691b072..2846576322c325 100644
--- a/zjit/src/backend/lir.rs
+++ b/zjit/src/backend/lir.rs
@@ -3228,7 +3228,7 @@ impl Assembler
let exit_block = self.new_block_without_id("side_exits");
// Map from SideExit to compiled Label. This table is used to deduplicate side exit code.
- let mut compiled_exits: HashMap = HashMap::new();
+ let mut compiled_exits: HashMap = HashMap::with_capacity(targets.len());
// Start a new perf range for side exits
let perf_symbol = if get_option!(perf) == Some(PerfMap::HIR) {
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index 437121381c7009..68ff278b6748db 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -650,6 +650,14 @@ pub type rb_jit_func_t = ::std::option::Option<
) -> VALUE,
>;
#[repr(C)]
+pub struct rb_iseq_variable {
+ pub flip_count: rb_snum_t,
+ pub script_lines: VALUE,
+ pub coverage: VALUE,
+ pub pc2branchindex: VALUE,
+ pub original_iseq: *mut VALUE,
+}
+#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct rb_iseq_constant_body_rb_iseq_parameters {
pub flags: rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1,
@@ -1260,24 +1268,21 @@ pub struct rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword {
pub default_values: *mut VALUE,
}
#[repr(C)]
-#[derive(Debug, Copy, Clone)]
+#[derive(Copy, Clone)]
pub struct rb_iseq_constant_body_iseq_insn_info {
pub body: *const iseq_insn_info_entry,
- pub positions: *mut ::std::os::raw::c_uint,
+ pub positions_or_succ_index_table: rb_iseq_constant_body_iseq_insn_info__bindgen_ty_1,
pub size: ::std::os::raw::c_uint,
- pub succ_index_table: *mut succ_index_table,
}
#[repr(C)]
-pub struct rb_iseq_constant_body__bindgen_ty_1 {
- pub flip_count: rb_snum_t,
- pub script_lines: VALUE,
- pub coverage: VALUE,
- pub pc2branchindex: VALUE,
- pub original_iseq: *mut VALUE,
+#[derive(Copy, Clone)]
+pub union rb_iseq_constant_body_iseq_insn_info__bindgen_ty_1 {
+ pub positions: *mut ::std::os::raw::c_uint,
+ pub succ_index_table: *mut succ_index_table,
}
#[repr(C)]
#[derive(Copy, Clone)]
-pub union rb_iseq_constant_body__bindgen_ty_2 {
+pub union rb_iseq_constant_body__bindgen_ty_1 {
pub list: *mut iseq_bits_t,
pub single: iseq_bits_t,
}
@@ -2094,7 +2099,7 @@ pub struct zjit_jit_frame {
pub stack: __IncompleteArrayField,
}
pub const ISEQ_BODY_OFFSET_PARAM: zjit_struct_offsets = 16;
-pub const ISEQ_BODY_OFFSET_OUTER_VARIABLES: zjit_struct_offsets = 288;
+pub const ISEQ_BODY_OFFSET_OUTER_VARIABLES: zjit_struct_offsets = 248;
pub const RUBY_OFFSET_THREAD_RACTOR: zjit_struct_offsets = 24;
pub type zjit_struct_offsets = u32;
#[repr(C)]