diff --git a/common.mk b/common.mk index 2a2f3b7ff3992a..dac615271ec1a9 100644 --- a/common.mk +++ b/common.mk @@ -1750,8 +1750,8 @@ $(UNICODE_EMOJI_FILES): $(Q) $(MAKEDIRS) "$(UNICODE_SRC_EMOJI_DATA_DIR)" $(Q) $(UNICODE_EMOJI_DOWNLOAD) $@ -$(srcdir)/lib/unicode_normalize/tables.rb: \ - $(UNICODE_SRC_DATA_DIR)/$(HAVE_BASERUBY:yes=.unicode-tables.time) +$(srcdir)/lib/unicode_normalize/$(HAVE_BASERUBY:yes=tables.rb): \ + $(UNICODE_SRC_DATA_DIR)/.unicode-tables.time $(UNICODE_SRC_DATA_DIR)/$(ALWAYS_UPDATE_UNICODE:yes=.unicode-tables.time): \ $(UNICODE_FILES) $(UNICODE_PROPERTY_FILES) \ diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 6b32f0d0d2baca..6bacc1c22162a2 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -752,7 +752,7 @@ rb_getnameinfo(const struct sockaddr *sa, socklen_t salen, int err = 0, gni_errno = 0; if (GETNAMEINFO_WONT_BLOCK(host, serv, flags)) { - return getnameinfo(sa, salen, host, hostlen, serv, servlen, flags); + return getnameinfo(sa, salen, host, (socklen_t)hostlen, serv, (socklen_t)servlen, flags); } start: diff --git a/shape.h b/shape.h index ce9f74d54a9322..065d3759074117 100644 --- a/shape.h +++ b/shape.h @@ -326,6 +326,7 @@ RSHAPE_LEN(shape_id_t shape_id) static inline attr_index_t RSHAPE_INDEX(shape_id_t shape_id) { + RUBY_ASSERT(RSHAPE_LEN(shape_id) > 0); return RSHAPE_LEN(shape_id) - 1; } diff --git a/string.c b/string.c index d873d93d8fdbd3..e022831ba5c8d5 100644 --- a/string.c +++ b/string.c @@ -9693,11 +9693,53 @@ rb_str_each_line(int argc, VALUE *argv, VALUE str) /* * call-seq: - * lines(Line_sep = $/, chomp: false) -> array_of_strings - * - * Forms substrings ("lines") of +self+ according to the given arguments - * (see String#each_line for details); returns the lines in an array. - * + * lines(record_separator = $/, chomp: false) -> array_of_strings + * + * Returns substrings ("lines") of +self+ + * according to the given arguments: + * + * s = <<~EOT + * This is the first line. + * This is line two. + * + * This is line four. + * This is line five. + * EOT + * + * With the default argument values: + * + * $/ # => "\n" + * s.lines + * # => + * ["This is the first line.\n", + * "This is line two.\n", + * "\n", + * "This is line four.\n", + * "This is line five.\n"] + * + * With a different +record_separator+: + * + * record_separator = ' is ' + * s.lines(record_separator) + * # => + * ["This is ", + * "the first line.\nThis is ", + * "line two.\n\nThis is ", + * "line four.\nThis is ", + * "line five.\n"] + * + * With keyword argument +chomp+ as +true+, + * removes the trailing newline from each line: + * + * s.lines(chomp: true) + * # => + * ["This is the first line.", + * "This is line two.", + * "", + * "This is line four.", + * "This is line five."] + * + * Related: see {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE diff --git a/variable.c b/variable.c index 967a7139ca8575..6d172defdd5d06 100644 --- a/variable.c +++ b/variable.c @@ -1895,57 +1895,8 @@ rb_obj_copy_fields_to_hash_table(VALUE obj, st_table *table) rb_field_foreach(obj, rb_obj_copy_ivs_to_hash_table_i, (st_data_t)table, false); } -attr_index_t -rb_obj_ivar_set(VALUE obj, ID id, VALUE val) -{ - shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); - - if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) { - goto too_complex; - } - - attr_index_t index; - if (!rb_shape_get_iv_index(current_shape_id, id, &index)) { - index = RSHAPE_LEN(current_shape_id); - if (index >= SHAPE_MAX_FIELDS) { - rb_raise(rb_eArgError, "too many instance variables"); - } - - shape_id_t next_shape_id = rb_shape_transition_add_ivar(obj, id); - if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { - current_shape_id = rb_evict_fields_to_hash(obj); - goto too_complex; - } - else if (UNLIKELY(RSHAPE_CAPACITY(next_shape_id) != RSHAPE_CAPACITY(current_shape_id))) { - RUBY_ASSERT(RSHAPE_CAPACITY(next_shape_id) > RSHAPE_CAPACITY(current_shape_id)); - rb_ensure_iv_list_size(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(next_shape_id)); - } - - RUBY_ASSERT(RSHAPE_TYPE_P(next_shape_id, SHAPE_IVAR), - "next_shape_id: 0x%" PRIx32 " RSHAPE_TYPE(next_shape_id): %d", - next_shape_id, (int)RSHAPE_TYPE(next_shape_id)); - RUBY_ASSERT(index == (RSHAPE_INDEX(next_shape_id))); - RBASIC_SET_SHAPE_ID(obj, next_shape_id); - } - - VALUE *table = ROBJECT_FIELDS(obj); - RB_OBJ_WRITE(obj, &table[index], val); - - return index; - -too_complex: - { - RUBY_ASSERT(rb_shape_obj_too_complex_p(obj)); - - st_table *table = ROBJECT_FIELDS_HASH(obj); - st_insert(table, (st_data_t)id, (st_data_t)val); - RB_OBJ_WRITTEN(obj, Qundef, val); - } - return 0; -} - -static void -obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) +static attr_index_t +obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val) { shape_id_t current_shape_id = RBASIC_SHAPE_ID(obj); @@ -1954,31 +1905,44 @@ obj_field_set(VALUE obj, shape_id_t target_shape_id, VALUE val) current_shape_id = rb_evict_fields_to_hash(obj); } - st_table *table = ROBJECT_FIELDS_HASH(obj); - if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { RBASIC_SET_SHAPE_ID(obj, target_shape_id); } - RUBY_ASSERT(RSHAPE_EDGE_NAME(target_shape_id)); - st_insert(table, (st_data_t)RSHAPE_EDGE_NAME(target_shape_id), (st_data_t)val); + if (!field_name) { + field_name = RSHAPE_EDGE_NAME(target_shape_id); + RUBY_ASSERT(field_name); + } + + st_insert(ROBJECT_FIELDS_HASH(obj), (st_data_t)field_name, (st_data_t)val); RB_OBJ_WRITTEN(obj, Qundef, val); + + return ATTR_INDEX_NOT_SET; } else { attr_index_t index = RSHAPE_INDEX(target_shape_id); - if (index >= RSHAPE_CAPACITY(current_shape_id)) { - rb_ensure_iv_list_size(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(target_shape_id)); - } - if (RSHAPE_LEN(target_shape_id) > RSHAPE_LEN(current_shape_id)) { + if (index >= RSHAPE_LEN(current_shape_id)) { + if (UNLIKELY(index >= RSHAPE_CAPACITY(current_shape_id))) { + rb_ensure_iv_list_size(obj, RSHAPE_CAPACITY(current_shape_id), RSHAPE_CAPACITY(target_shape_id)); + } RBASIC_SET_SHAPE_ID(obj, target_shape_id); } - VALUE *table = ROBJECT_FIELDS(obj); - RB_OBJ_WRITE(obj, &table[index], val); + RB_OBJ_WRITE(obj, &ROBJECT_FIELDS(obj)[index], val); + + return index; } } +attr_index_t +rb_obj_ivar_set(VALUE obj, ID id, VALUE val) +{ + bool dontcare; + shape_id_t target_shape_id = generic_shape_ivar(obj, id, &dontcare); + return obj_field_set(obj, target_shape_id, id, val); +} + /* Set the instance variable +val+ on object +obj+ at ivar name +id+. * This function only works with T_OBJECT objects, so make sure * +obj+ is of type T_OBJECT before using this function. @@ -2052,7 +2016,7 @@ rb_obj_field_set(VALUE obj, shape_id_t target_shape_id, ID field_name, VALUE val { switch (BUILTIN_TYPE(obj)) { case T_OBJECT: - obj_field_set(obj, target_shape_id, val); + obj_field_set(obj, target_shape_id, field_name, val); break; case T_CLASS: case T_MODULE: @@ -4571,43 +4535,30 @@ rb_iv_set(VALUE obj, const char *name, VALUE val) static bool class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool concurrent, VALUE *new_fields_obj) { - bool existing = true; const VALUE original_fields_obj = fields_obj; fields_obj = original_fields_obj ? original_fields_obj : rb_imemo_fields_new(klass, 1); shape_id_t current_shape_id = RBASIC_SHAPE_ID(fields_obj); - shape_id_t next_shape_id = current_shape_id; if (UNLIKELY(rb_shape_too_complex_p(current_shape_id))) { goto too_complex; } - attr_index_t index; - if (!rb_shape_get_iv_index(current_shape_id, id, &index)) { - existing = false; - - index = RSHAPE_LEN(current_shape_id); - if (index >= SHAPE_MAX_FIELDS) { - rb_raise(rb_eArgError, "too many instance variables"); - } - - next_shape_id = rb_shape_transition_add_ivar(fields_obj, id); - if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { - fields_obj = imemo_fields_complex_from_obj(klass, fields_obj, next_shape_id); - goto too_complex; - } + bool new_ivar; + shape_id_t next_shape_id = generic_shape_ivar(fields_obj, id, &new_ivar); - attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id); - attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id); + if (UNLIKELY(rb_shape_too_complex_p(next_shape_id))) { + fields_obj = imemo_fields_complex_from_obj(klass, fields_obj, next_shape_id); + goto too_complex; + } - if (next_capacity > current_capacity) { + attr_index_t index = RSHAPE_INDEX(next_shape_id); + if (new_ivar) { + if (index >= RSHAPE_CAPACITY(current_shape_id)) { // We allocate a new fields_obj even when concurrency isn't a concern // so that we're embedded as long as possible. - fields_obj = imemo_fields_copy_capa(klass, fields_obj, next_capacity); + fields_obj = imemo_fields_copy_capa(klass, fields_obj, RSHAPE_CAPACITY(next_shape_id)); } - - RUBY_ASSERT(RSHAPE(next_shape_id)->type == SHAPE_IVAR); - RUBY_ASSERT(index == (RSHAPE_LEN(next_shape_id) - 1)); } VALUE *fields = rb_imemo_fields_ptr(fields_obj); @@ -4624,12 +4575,12 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc RB_OBJ_WRITE(fields_obj, &fields[index], val); } - if (!existing) { + if (new_ivar) { RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); } *new_fields_obj = fields_obj; - return existing; + return new_ivar; too_complex: { @@ -4641,7 +4592,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc } st_table *table = rb_imemo_fields_complex_tbl(fields_obj); - existing = st_insert(table, (st_data_t)id, (st_data_t)val); + new_ivar = !st_insert(table, (st_data_t)id, (st_data_t)val); RB_OBJ_WRITTEN(fields_obj, Qundef, val); if (fields_obj != original_fields_obj) { @@ -4650,7 +4601,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc } *new_fields_obj = fields_obj; - return existing; + return new_ivar; } bool @@ -4664,7 +4615,7 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val) const VALUE original_fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj); VALUE new_fields_obj = 0; - bool existing = class_fields_ivar_set(obj, original_fields_obj, id, val, rb_multi_ractor_p(), &new_fields_obj); + bool new_ivar = class_fields_ivar_set(obj, original_fields_obj, id, val, rb_multi_ractor_p(), &new_fields_obj); if (new_fields_obj != original_fields_obj) { RCLASS_WRITABLE_SET_FIELDS_OBJ(obj, new_fields_obj); @@ -4676,7 +4627,7 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val) // Perhaps INVALID_SHAPE_ID? RBASIC_SET_SHAPE_ID(obj, RBASIC_SHAPE_ID(new_fields_obj)); - return !existing; + return new_ivar; } void @@ -4713,4 +4664,3 @@ rb_const_lookup(VALUE klass, ID id) { return const_lookup(RCLASS_CONST_TBL(klass), id); } - diff --git a/win32/Makefile.sub b/win32/Makefile.sub index 1bdef106b33ed5..3312faa7d6c864 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -17,7 +17,9 @@ tooldir = $(srcdir)/tool MFLAGS=-l !endif -!if "$(BASERUBY)" == "" +!if "$(HAVE_BASERUBY)" == "no" +BASERUBY = +!else if "$(BASERUBY)" == "" # After `nmake`, just built `ruby.exe` exists in the build directory, # and is searched first prior to $PATH. Assume that no ruby # executable in $(tooldir). @@ -28,7 +30,7 @@ MFLAGS=-l ! endif ! if [del baseruby.mk 2> nul] ! endif -!else if "$(BASERUBY)" == "no" || [($(BASERUBY) -eexit) > nul 2> nul] +!else if [($(BASERUBY) $(tooldir)/missing-baseruby.bat) > nul 2> nul] BASERUBY = !endif !if "$(BASERUBY)" == "" @@ -122,9 +124,6 @@ TOUCH = $(BASERUBY) -run -e touch -- CP = copy > nul MV = move > nul RM1 = del /f /q -!if !defined(BASERUBY) -BASERUBY = ruby -!endif !if !defined(TEST_RUNNABLE) TEST_RUNNABLE = yes !endif diff --git a/win32/configure.bat b/win32/configure.bat index bad09cd3f5964a..79384a87590067 100755 --- a/win32/configure.bat +++ b/win32/configure.bat @@ -51,7 +51,7 @@ if "%1" == "--disable-rubygems" goto :disable-rubygems if "%1" == "--extout" goto :extout if "%1" == "--path" goto :path if "%1" == "--with-baseruby" goto :baseruby -if "%1" == "--without-baseruby" goto :baseruby +if "%1" == "--without-baseruby" goto :nobaseruby if "%1" == "--with-ntver" goto :ntver if "%1" == "--with-libdir" goto :libdir if "%1" == "--with-git" goto :git @@ -208,6 +208,7 @@ goto :loop ; shift goto :loop ; :baseruby + echo>> %config_make% HAVE_BASERUBY = echo>> %config_make% BASERUBY = %~2 echo>>%confargs% %1=%2 \ shift @@ -215,7 +216,8 @@ goto :loop ; goto :loop ; :nobaseruby echo>> %config_make% HAVE_BASERUBY = no - echo>>%confargs% %1=%2 \ + echo>> %config_make% BASERUBY = + echo>>%confargs% %1 \ shift goto :loop ; :libdir @@ -238,8 +240,7 @@ goto :loop ; goto :loop ; :gmp echo>> %config_make% WITH_GMP = yes - echo>>%confargs% %1=1 \ - shift + echo>>%confargs% %1 \ shift goto :loop ; :gmp-dir diff --git a/win32/setup.mak b/win32/setup.mak index 275ccda3bb6578..15fa41b5622651 100644 --- a/win32/setup.mak +++ b/win32/setup.mak @@ -46,7 +46,7 @@ prefix = $(prefix:\=/) << @type $(config_make) >>$(MAKEFILE) @del $(config_make) > nul -!if defined(BASERUBY) +!if "$(HAVE_BASERUBY)" != "no" && "$(BASERUBY)" != "" $(BASERUBY:/=\) "$(srcdir)/tool/missing-baseruby.bat" !endif !if "$(WITH_GMP)" != "no" diff --git a/zjit.rb b/zjit.rb index 871b1a91ca8a1f..e07bccb132f1d0 100644 --- a/zjit.rb +++ b/zjit.rb @@ -39,19 +39,31 @@ def stats(key = nil) # Get the summary of ZJIT statistics as a String def stats_string - buf = +'' + buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats - [ + print_counters_with_prefix(prefix: 'failed_', prompt: 'compilation failure reasons', buf:, stats:) + print_counters([ + :compiled_iseq_count, + :compilation_failure, + :compile_time_ns, :profile_time_ns, :gc_time_ns, :invalidation_time_ns, + :total_insns_count, :vm_insns_count, :zjit_insns_count, :ratio_in_zjit, - ].each do |key| + ], buf:, stats:) + + buf + end + + def print_counters(keys, buf:, stats:) + left_pad = keys.map(&:size).max + 1 + keys.each do |key| # Some stats like vm_insns_count and ratio_in_zjit are not supported on the release build next unless stats.key?(key) value = stats[key] @@ -66,9 +78,16 @@ def stats_string value = number_with_delimiter(value) end - buf << "#{'%-18s' % "#{key}:"} #{value}\n" + buf << "#{"%-#{left_pad}s" % "#{key}:"} #{value}\n" + end + end + + def print_counters_with_prefix(buf:, stats:, prefix:, prompt:) + keys = stats.keys.select { |key| key.start_with?(prefix) && stats[key] > 0 } + unless keys.empty? + buf << "#{prompt}:\n" + print_counters(keys, buf:, stats:) end - buf end # Assert that any future ZJIT compilation will return a function pointer diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 7ff5564d220304..ed0c52a91169a0 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -6,8 +6,9 @@ use std::slice; use crate::asm::Label; use crate::backend::current::{Reg, ALLOC_REGS}; use crate::invariants::{track_bop_assumption, track_cme_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption}; -use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqStatus}; +use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqPayload, IseqStatus}; use crate::state::ZJITState; +use crate::stats::incr_counter; use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::compile_time_ns}; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; 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, SCRATCH_OPND, SP}; @@ -112,11 +113,14 @@ fn gen_iseq_entry_point(iseq: IseqPtr) -> *const u8 { /// Compile an entry point for a given ISEQ fn gen_iseq_entry_point_body(cb: &mut CodeBlock, iseq: IseqPtr) -> Option { // Compile ISEQ into High-level IR - let function = compile_iseq(iseq)?; + let Some(function) = compile_iseq(iseq) else { + incr_counter!(compilation_failure); + return None; + }; // Compile the High-level IR - let Some((start_ptr, gc_offsets, jit)) = gen_function(cb, iseq, &function) else { - debug!("Failed to compile iseq: gen_function failed: {}", iseq_get_location(iseq, 0)); + let Some(start_ptr) = gen_iseq(cb, iseq, Some(&function)) else { + debug!("Failed to compile iseq: gen_iseq failed: {}", iseq_get_location(iseq, 0)); return None; }; @@ -126,17 +130,6 @@ fn gen_iseq_entry_point_body(cb: &mut CodeBlock, iseq: IseqPtr) -> Option Option<(CodePtr, Vec>>)> { +/// Compile an ISEQ into machine code if not compiled yet +fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> Option { // Return an existing pointer if it's already compiled let payload = get_or_create_iseq_payload(iseq); match payload.status { - IseqStatus::Compiled(start_ptr) => return Some((start_ptr, vec![])), + IseqStatus::Compiled(start_ptr) => return Some(start_ptr), IseqStatus::CantCompile => return None, IseqStatus::NotCompiled => {}, } - // Convert ISEQ into High-level IR and optimize HIR - let function = match compile_iseq(iseq) { + // Compile the ISEQ + let code_ptr = gen_iseq_body(cb, iseq, function, payload); + if let Some(start_ptr) = code_ptr { + payload.status = IseqStatus::Compiled(start_ptr); + incr_counter!(compiled_iseq_count); + } else { + payload.status = IseqStatus::CantCompile; + incr_counter!(compilation_failure); + } + code_ptr +} + +/// Compile an ISEQ into machine code +fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, payload: &mut IseqPayload) -> Option { + // Convert ISEQ into optimized High-level IR if not given + let function = match function { Some(function) => function, - None => { - payload.status = IseqStatus::CantCompile; - return None; - } + None => &compile_iseq(iseq)?, }; // Compile the High-level IR - let result = gen_function(cb, iseq, &function); - if let Some((start_ptr, gc_offsets, jit)) = result { - payload.status = IseqStatus::Compiled(start_ptr); - payload.iseq_calls.extend(jit.iseq_calls.clone()); - append_gc_offsets(iseq, &gc_offsets); - Some((start_ptr, jit.iseq_calls)) - } else { - payload.status = IseqStatus::CantCompile; - None + let (start_ptr, gc_offsets, iseq_calls) = gen_function(cb, iseq, function)?; + + // Stub callee ISEQs for JIT-to-JIT calls + for iseq_call in iseq_calls.iter() { + gen_iseq_call(cb, iseq, iseq_call)?; } + + // Prepare for GC + payload.iseq_calls.extend(iseq_calls.clone()); + append_gc_offsets(iseq, &gc_offsets); + Some(start_ptr) } /// Compile a function -fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec, JITState)> { +fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec, Vec>>)> { let c_stack_slots = max_num_params(function).saturating_sub(ALLOC_REGS.len()); let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_slots); let mut asm = Assembler::new(); @@ -279,6 +283,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio let insn = function.find(insn_id); if gen_insn(cb, &mut jit, &mut asm, function, insn_id, &insn).is_none() { debug!("Failed to compile insn: {insn_id} {insn}"); + incr_counter!(failed_gen_insn); return None; } } @@ -291,8 +296,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio } // Generate code if everything can be compiled - let result = asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit)); - if let Some((start_ptr, _, _)) = result { + let result = asm.compile(cb); + if let Some((start_ptr, _)) = result { if get_option!(perf) { let start_usize = start_ptr.raw_ptr(cb) as usize; let end_usize = cb.get_write_ptr().raw_ptr(cb) as usize; @@ -304,8 +309,10 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio let iseq_name = iseq_get_location(iseq, 0); ZJITState::log_compile(iseq_name); } + } else { + incr_counter!(failed_asm_compile); } - result + result.map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit.iseq_calls)) } /// Compile an instruction @@ -410,6 +417,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio | Insn::Const { .. } => { debug!("ZJIT: gen_function: unexpected insn {insn}"); + incr_counter!(failed_gen_insn_unexpected); return None; } }; @@ -1372,6 +1380,7 @@ fn compile_iseq(iseq: IseqPtr) -> Option { Err(err) => { let name = crate::cruby::iseq_get_location(iseq, 0); debug!("ZJIT: iseq_to_hir: {err:?}: {name}"); + incr_counter!(failed_hir_compile); return None; } }; @@ -1382,6 +1391,7 @@ fn compile_iseq(iseq: IseqPtr) -> Option { #[cfg(debug_assertions)] if let Err(err) = function.validate() { debug!("ZJIT: compile_iseq: {err:?}"); + incr_counter!(failed_hir_optimize); return None; } Some(function) @@ -1516,16 +1526,11 @@ c_callable! { /// Compile an ISEQ for a function stub fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc>) -> Option { // Compile the stubbed ISEQ - let Some((code_ptr, iseq_calls)) = gen_iseq(cb, iseq_call.borrow().iseq) else { + let Some(code_ptr) = gen_iseq(cb, iseq_call.borrow().iseq, None) else { debug!("Failed to compile iseq: gen_iseq failed: {}", iseq_get_location(iseq_call.borrow().iseq, 0)); return None; }; - // Stub callee ISEQs for JIT-to-JIT calls - for callee_iseq_call in iseq_calls.iter() { - gen_iseq_call(cb, iseq_call.borrow().iseq, callee_iseq_call)?; - } - // Update the stub to call the code pointer let code_addr = code_ptr.raw_ptr(cb); let iseq = iseq_call.borrow().iseq; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1cd31497d8e0eb..99dfdbaa6e8ef1 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4,7 +4,7 @@ #![allow(non_upper_case_globals)] use crate::{ - cast::IntoUsize, cruby::*, gc::{get_or_create_iseq_payload, IseqPayload}, options::{get_option, DumpHIR}, state::ZJITState, stats::Counter + cast::IntoUsize, cruby::*, gc::{get_or_create_iseq_payload, IseqPayload}, options::{get_option, DumpHIR}, state::ZJITState }; use std::{ cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_int, c_void, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter @@ -12,6 +12,7 @@ use std::{ use crate::hir_type::{Type, types}; use crate::bitset::BitSet; use crate::profile::{TypeDistributionSummary, ProfiledType}; +use crate::stats::{incr_counter, Counter}; /// An index of an [`Insn`] in a [`Function`]. This is a popular /// type since this effectively acts as a pointer to an [`Insn`]. @@ -3494,6 +3495,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.profiles = Some(profiles); if let Err(e) = fun.validate() { + incr_counter!(failed_hir_compile_validate); return Err(ParseError::Validation(e)); } Ok(fun) diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index ce185597c402d0..b17edcb3a0823f 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -1,6 +1,6 @@ use std::time::Instant; -use crate::{cruby::*, options::get_option, state::{zjit_enabled_p, ZJITState}}; +use crate::{cruby::*, options::get_option, state::zjit_enabled_p}; macro_rules! make_counters { ( @@ -42,11 +42,17 @@ macro_rules! make_counters { } } - /// The list of counters that are available without --zjit-stats. + /// List of counters that are available without --zjit-stats. /// They are incremented only by `incr_counter()` and don't use `gen_incr_counter()`. pub const DEFAULT_COUNTERS: &'static [Counter] = &[ $( Counter::$default_counter_name, )+ ]; + + /// List of all counters + pub const ALL_COUNTERS: &'static [Counter] = &[ + $( Counter::$default_counter_name, )+ + $( Counter::$counter_name, )+ + ]; } } @@ -54,6 +60,9 @@ macro_rules! make_counters { make_counters! { // Default counters that are available without --zjit-stats default { + compiled_iseq_count, + compilation_failure, + compile_time_ns, profile_time_ns, gc_time_ns, @@ -62,14 +71,30 @@ make_counters! { // The number of times YARV instructions are executed on JIT code zjit_insns_count, + + // failed_: Compilation failure reasons + failed_hir_compile, + failed_hir_compile_validate, + failed_hir_optimize, + failed_gen_insn, + failed_gen_insn_unexpected, + failed_asm_compile, } /// Increase a counter by a specified amount -fn incr_counter(counter: Counter, amount: u64) { +pub fn incr_counter_by(counter: Counter, amount: u64) { let ptr = counter_ptr(counter); unsafe { *ptr += amount; } } +/// Increment a counter by its identifier +macro_rules! incr_counter { + ($counter_name:ident) => { + $crate::stats::incr_counter_by($crate::stats::Counter::$counter_name, 1) + } +} +pub(crate) use incr_counter; + /// Return a Hash object that contains ZJIT statistics #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> VALUE { @@ -95,19 +120,22 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> } else { Qnil }; - let counters = ZJITState::get_counters(); - for &counter in DEFAULT_COUNTERS { - set_stat!(hash, &counter.name(), unsafe { *counter_ptr(counter) }); + // If not --zjit-stats, set only default counters + if !get_option!(stats) { + for &counter in DEFAULT_COUNTERS { + set_stat!(hash, &counter.name(), unsafe { *counter_ptr(counter) }); + } + return hash; } - // Set counters that are enabled when --zjit-stats is enabled - if get_option!(stats) { - set_stat!(hash, "zjit_insns_count", counters.zjit_insns_count); + // Set all counters for --zjit-stats + for &counter in ALL_COUNTERS { + set_stat!(hash, &counter.name(), unsafe { *counter_ptr(counter) }); + } - if unsafe { rb_vm_insns_count } > 0 { - set_stat!(hash, "vm_insns_count", unsafe { rb_vm_insns_count }); - } + if unsafe { rb_vm_insns_count } > 0 { + set_stat!(hash, "vm_insns_count", unsafe { rb_vm_insns_count }); } hash @@ -118,7 +146,7 @@ pub fn with_time_stat(counter: Counter, func: F) -> R where F: FnOnce() -> let start = Instant::now(); let ret = func(); let nanos = Instant::now().duration_since(start).as_nanos(); - incr_counter(counter, nanos as u64); + incr_counter_by(counter, nanos as u64); ret }