diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index be9bbec4098472..a8730fb59c5b42 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -181,11 +181,18 @@ jobs: strategy: matrix: include: + # Run with the inliner enabled (on by default) # Test --call-threshold=2 with 2 iterations in total - ruby_opts: '--zjit-call-threshold=2' bench_opts: '--warmup=1 --bench=1 --excludes=shipit' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow + # Run with inliner disabled # + # Test --call-threshold=2 with 2 iterations in total + - ruby_opts: '--zjit-inline-threshold=0 --zjit-call-threshold=2' + bench_opts: '--warmup=1 --bench=1 --excludes=shipit' + configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow + runs-on: macos-26 if: >- diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 25e89d21bb4ac8..0771e479317f57 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -58,28 +58,43 @@ jobs: fail-fast: false matrix: include: + - test_task: 'check' + run_opts: '--zjit-disable-hir-opt --zjit-call-threshold=1' + specopts: '-T --zjit-disable-hir-opt -T --zjit-call-threshold=1' + configure: '--enable-zjit=dev' + + ## Run with default options (inliner enabled) ## - test_task: 'check' run_opts: '--zjit-call-threshold=1' specopts: '-T --zjit-call-threshold=1' configure: '--enable-zjit=dev' - - test_task: 'check' - run_opts: '--zjit-call-threshold=1 --zjit-inline-threshold=30' - specopts: '-T --zjit-call-threshold=1 -T --zjit-inline-threshold=30' + # The optimizer benefits from at least 1 iteration of profiling. Also, many + # regression tests in bootstraptest/test_yjit.rb assume call-threshold=2. + - test_task: 'btest' + run_opts: '--zjit-call-threshold=2' configure: '--enable-zjit=dev' - continue-on-test_task: true + - test_task: 'test-bundled-gems' + configure: '--enable-zjit=dev' + run_opts: '--zjit-call-threshold=1' + + ## Run with inliner disabled ## - test_task: 'check' - run_opts: '--zjit-disable-hir-opt --zjit-call-threshold=1' - specopts: '-T --zjit-disable-hir-opt -T --zjit-call-threshold=1' + run_opts: '--zjit-inline-threshold=0 --zjit-call-threshold=1' + specopts: '-T --zjit-inline-threshold=0 -T --zjit-call-threshold=1' configure: '--enable-zjit=dev' # The optimizer benefits from at least 1 iteration of profiling. Also, many # regression tests in bootstraptest/test_yjit.rb assume call-threshold=2. - test_task: 'btest' - run_opts: '--zjit-call-threshold=2' + run_opts: '--zjit-inline-threshold=0 --zjit-call-threshold=2' configure: '--enable-zjit=dev' + - test_task: 'test-bundled-gems' + configure: '--enable-zjit=dev' + run_opts: '--zjit-inline-threshold=0 --zjit-call-threshold=1' + - test_task: 'zjit-check' # zjit-test + quick feedback of test_zjit.rb configure: '--enable-yjit --enable-zjit=dev' rust_version: '1.85.0' @@ -91,10 +106,6 @@ jobs: clang_path: '/usr/bin/clang-16' runs-on: 'ubuntu-24.04' # for clang-16 - - test_task: 'test-bundled-gems' - configure: '--enable-zjit=dev' - run_opts: '--zjit-call-threshold=1' - env: GITPULLOPTIONS: --no-tags origin ${{ github.ref }} RUN_OPTS: ${{ matrix.run_opts }} @@ -246,11 +257,14 @@ jobs: strategy: matrix: include: - # Test --call-threshold=2 with 2 iterations in total + # Test --call-threshold=2 with 2 iterations in total (inlining enabled) - ruby_opts: '--zjit-call-threshold=2' bench_opts: '--warmup=1 --bench=1 --excludes=shipit' configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow - + # Test --call-threshold=2 with 2 iterations in total (inlining disabled) + - ruby_opts: '--zjit-inline-threshold=0 --zjit-call-threshold=2' + bench_opts: '--warmup=1 --bench=1 --excludes=shipit' + configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow runs-on: ubuntu-24.04 if: >- diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index 435ebdd43da5c8..4e9c470ec195e1 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -510,24 +510,6 @@ def gzip_to(io) # :yields: gz_io gz_io.close end - ## - # Returns the full path for installing +filename+. - # - # If +filename+ is not inside +destination_dir+ an exception is raised. - - def install_location(filename, destination_dir) # :nodoc: - raise Gem::Package::PathError.new(filename, destination_dir) if - filename.start_with? "/" - - destination_dir = File.realpath(destination_dir) - destination = File.expand_path(filename, destination_dir) - - raise Gem::Package::PathError.new(destination, destination_dir) unless - normalize_path(destination).start_with? normalize_path(destination_dir + "/") - - destination - end - if Gem.win_platform? def normalize_path(pathname) # :nodoc: pathname.downcase @@ -662,6 +644,24 @@ def verify private + ## + # Returns the full path for installing +filename+ into +destination_dir+, + # which must already be resolved with File.realpath by the caller. + # + # If +filename+ is not inside +destination_dir+ an exception is raised. + + def install_location(filename, destination_dir) # :nodoc: + raise Gem::Package::PathError.new(filename, destination_dir) if + filename.start_with? "/" + + destination = File.expand_path(filename, destination_dir) + + raise Gem::Package::PathError.new(destination, destination_dir) unless + normalize_path(destination).start_with? normalize_path(destination_dir + "/") + + destination + end + ## # Verifies the +checksums+ against the +digests+. This check is not # cryptographically secure. Missing checksums are ignored. diff --git a/lib/rubygems/package/old.rb b/lib/rubygems/package/old.rb index 1a13ac3e297a72..e06be05bc0a15e 100644 --- a/lib/rubygems/package/old.rb +++ b/lib/rubygems/package/old.rb @@ -59,6 +59,9 @@ def extract_files(destination_dir) header = file_list io raise Gem::Exception, errstr unless header + # install_location expects an already-resolved destination dir + destination_dir = File.realpath(destination_dir) + header.each do |entry| full_name = entry["path"] diff --git a/ractor.c b/ractor.c index 03c949d5ba9e04..58677a0b470dae 100644 --- a/ractor.c +++ b/ractor.c @@ -1461,11 +1461,29 @@ allow_frozen_shareable_p(VALUE obj) return false; } +static void +make_shareable_freeze(VALUE obj) +{ + VALUE klass = RBASIC_CLASS(obj); + if (klass == rb_cString && BASIC_OP_UNREDEFINED_P(BOP_FREEZE, STRING_REDEFINED_OP_FLAG)) { + rb_str_freeze(obj); + } + else if (klass == rb_cArray && BASIC_OP_UNREDEFINED_P(BOP_FREEZE, ARRAY_REDEFINED_OP_FLAG)) { + rb_ary_freeze(obj); + } + else if (klass == rb_cHash && BASIC_OP_UNREDEFINED_P(BOP_FREEZE, HASH_REDEFINED_OP_FLAG)) { + rb_hash_freeze(obj); + } + else { + rb_funcall(obj, idFreeze, 0); + } +} + static enum obj_traverse_iterator_result make_shareable_check_shareable_freeze(VALUE obj, enum obj_traverse_iterator_result result) { if (!RB_OBJ_FROZEN_RAW(obj)) { - rb_funcall(obj, idFreeze, 0); + make_shareable_freeze(obj); if (UNLIKELY(!RB_OBJ_FROZEN_RAW(obj))) { rb_raise(rb_eRactorError, "#freeze does not freeze object correctly"); diff --git a/test/rubygems/test_gem_package.rb b/test/rubygems/test_gem_package.rb index b69002486d4112..be7cfd2843f588 100644 --- a/test/rubygems/test_gem_package.rb +++ b/test/rubygems/test_gem_package.rb @@ -835,79 +835,100 @@ def test_extract_tar_gz_case_insensitive end end - def test_install_location - package = Gem::Package.new @gem - - file = "file.rb".dup - - destination = package.install_location file, @destination - - assert_equal File.join(@destination, "file.rb"), destination - end + # The following tests exercise install_location's path resolution and + # traversal protection through the real extraction path (extract_tar_gz) + # rather than calling the private helper directly. The absolute-path case is + # already covered by test_extract_tar_gz_absolute. - def test_install_location_absolute + def test_extract_tar_gz_basic_file package = Gem::Package.new @gem - e = assert_raise Gem::Package::PathError do - package.install_location "/absolute.rb", @destination + tgz_io = util_tar_gz do |tar| + tar.add_file "file.rb", 0o644 do |io| + io.write "hi" + end end - assert_equal("installing into parent path /absolute.rb of " \ - "#{@destination} is not allowed", e.message) + package.extract_tar_gz tgz_io, @destination + + extracted = File.join @destination, "file.rb" + assert_path_exist extracted + assert_equal "hi", File.read(extracted) end - def test_install_location_dots + def test_extract_tar_gz_collapses_parent_dots package = Gem::Package.new @gem - file = "file.rb" - - destination = File.join @destination, "foo", "..", "bar" - - FileUtils.mkdir_p File.join @destination, "foo" - FileUtils.mkdir_p File.expand_path destination + tgz_io = util_tar_gz do |tar| + tar.add_file "foo/../bar/file.rb", 0o644 do |io| + io.write "hi" + end + end - destination = package.install_location file, destination + package.extract_tar_gz tgz_io, @destination - # this test only fails on ruby missing File.realpath - assert_equal File.join(@destination, "bar", "file.rb"), destination + extracted = File.join @destination, "bar", "file.rb" + assert_path_exist extracted + assert_equal "hi", File.read(extracted) + assert_path_not_exist File.join(@destination, "foo") end - def test_install_location_extra_slash + def test_extract_tar_gz_collapses_extra_slash package = Gem::Package.new @gem - file = "foo//file.rb".dup + tgz_io = util_tar_gz do |tar| + tar.add_file "foo//file.rb", 0o644 do |io| + io.write "hi" + end + end - destination = package.install_location file, @destination + package.extract_tar_gz tgz_io, @destination - assert_equal File.join(@destination, "foo", "file.rb"), destination + extracted = File.join @destination, "foo", "file.rb" + assert_path_exist extracted + assert_equal "hi", File.read(extracted) end - def test_install_location_relative + def test_extract_tar_gz_rejects_relative_escape package = Gem::Package.new @gem + tgz_io = util_tar_gz do |tar| + tar.add_file "../relative.rb", 0o644 do |io| + io.write "hi" + end + end + e = assert_raise Gem::Package::PathError do - package.install_location "../relative.rb", @destination + package.extract_tar_gz tgz_io, @destination end parent = File.expand_path File.join @destination, "../relative.rb" assert_equal("installing into parent path #{parent} of " \ "#{@destination} is not allowed", e.message) + assert_path_not_exist parent end - def test_install_location_suffix + def test_extract_tar_gz_rejects_suffix_escape package = Gem::Package.new @gem filename = "../#{File.basename(@destination)}suffix.rb" + tgz_io = util_tar_gz do |tar| + tar.add_file filename, 0o644 do |io| + io.write "hi" + end + end + e = assert_raise Gem::Package::PathError do - package.install_location filename, @destination + package.extract_tar_gz tgz_io, @destination end parent = File.expand_path File.join @destination, filename assert_equal("installing into parent path #{parent} of " \ "#{@destination} is not allowed", e.message) + assert_path_not_exist parent end def test_load_spec_from_metadata diff --git a/time.c b/time.c index 24a09e88a77458..d6826d7aad9ac8 100644 --- a/time.c +++ b/time.c @@ -256,18 +256,41 @@ divmodv(VALUE n, VALUE d, VALUE *q, VALUE *r) #define FIXWV_P(w) FIXWINT_P(WIDEVAL_GET(w)) #define MUL_OVERFLOW_FIXWV_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, FIXWV_MIN, FIXWV_MAX) -/* #define STRUCT_WIDEVAL */ -#ifdef STRUCT_WIDEVAL - /* for type checking */ +/* .value holds the markable VALUE; on 32-bit the wide int's high bits go in .hi */ +#if WIDEVALUE_IS_WIDER && SIZEOF_VALUE < SIZEOF_INT64_T typedef struct { - WIDEVALUE value; + VALUE value; + uint32_t hi; } wideval_t; - static inline wideval_t WIDEVAL_WRAP(WIDEVALUE v) { wideval_t w = { v }; return w; } -# define WIDEVAL_GET(w) ((w).value) + static inline wideval_t + WIDEVAL_WRAP(WIDEVALUE v) + { + wideval_t w; + w.value = (VALUE)(uint32_t)v; + w.hi = (uint32_t)(v >> 32); + return w; + } + static inline WIDEVALUE + WIDEVAL_GET(wideval_t w) + { + return ((WIDEVALUE)w.hi << 32) | (uint32_t)w.value; + } #else - typedef WIDEVALUE wideval_t; -# define WIDEVAL_WRAP(v) (v) -# define WIDEVAL_GET(w) (w) + typedef struct { + VALUE value; + } wideval_t; + static inline wideval_t + WIDEVAL_WRAP(WIDEVALUE v) + { + wideval_t w; + w.value = (VALUE)v; + return w; + } + static inline WIDEVALUE + WIDEVAL_GET(wideval_t w) + { + return (WIDEVALUE)w.value; + } #endif #if WIDEVALUE_IS_WIDER @@ -1888,28 +1911,23 @@ force_make_tm(VALUE time, struct time_object *tobj) time_get_tm(time, tobj); } -static void -time_mark_and_move(void *ptr) -{ - struct time_object *tobj = ptr; - if (!WIDEVALUE_IS_WIDER || !FIXWV_P(tobj->timew)) { - rb_gc_mark_and_move((VALUE *)&WIDEVAL_GET(tobj->timew)); - } - rb_gc_mark_and_move(&tobj->vtm.year); - rb_gc_mark_and_move(&tobj->vtm.subsecx); - rb_gc_mark_and_move(&tobj->vtm.utc_offset); - rb_gc_mark_and_move(&tobj->vtm.zone); -} +RUBY_REFERENCES(time_refs) = { + RUBY_REF_EDGE(struct time_object, timew.value), + RUBY_REF_EDGE(struct time_object, vtm.year), + RUBY_REF_EDGE(struct time_object, vtm.subsecx), + RUBY_REF_EDGE(struct time_object, vtm.utc_offset), + RUBY_REF_EDGE(struct time_object, vtm.zone), + RUBY_REF_END +}; static const rb_data_type_t time_data_type = { .wrap_struct_name = "time", .function = { - .dmark = time_mark_and_move, + RUBY_REFS_LIST_PTR(time_refs), .dfree = RUBY_TYPED_DEFAULT_FREE, .dsize = NULL, - .dcompact = time_mark_and_move, }, - .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE | RUBY_TYPED_DECL_MARKING, }; static VALUE @@ -5786,9 +5804,9 @@ tm_from_time(VALUE klass, VALUE time) ttm = RTYPEDDATA_GET_DATA(tm); v = &vtm; - WIDEVALUE timew = tobj->timew; + wideval_t timew = tobj->timew; GMTIMEW(timew, v); - time_set_timew(tm, ttm, wsub(timew, v->subsecx)); + time_set_timew(tm, ttm, wsub(timew, v2w(v->subsecx))); v->subsecx = INT2FIX(0); v->zone = Qnil; time_set_vtm(tm, ttm, *v); diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index fda896860ab4a1..4294cb377305de 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1431,7 +1431,8 @@ impl Assembler { // First operand is popped from the lower stack address ldp_post(cb, first_pop, second_pop, A64Opnd::new_mem(64, C_SP_REG, C_SP_STEP)); }, - Insn::CCall { fptr, .. } => { + Insn::CCall { data, .. } => { + let fptr = &data.fptr; match fptr { Opnd::UImm(fptr) => { // The offset to the call target in bytes @@ -1773,7 +1774,7 @@ mod tests { let val64 = asm.add(CFP, Opnd::UImm(64)); asm.store(Opnd::mem(64, SP, 0x10), val64); - let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, exit: SideExit { pc: 0.into(), iseq: std::ptr::null(), stack: vec![], locals: vec![], recompile: None } }; + let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, exit: Box::new(SideExit { pc: 0.into(), iseq: std::ptr::null(), stack: vec![], locals: vec![], recompile: None }) }; asm.push_insn(Insn::Joz(val64, side_exit)); asm.mov(C_ARG_OPNDS[0], C_RET_OPND.with_num_bits(32)); asm.mov(C_ARG_OPNDS[1], Opnd::mem(64, SP, -8)); diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 675c1c004b8ea0..da4fe79b7c6c81 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -577,8 +577,9 @@ pub enum Target Block(BranchEdge), /// Side exit to the interpreter SideExit { - /// Context used for compiling the side exit - exit: SideExit, + /// Context used for compiling the side exit. Boxed to keep `Target` + /// (and every `Insn` variant that embeds it) small. + exit: Box, /// We use this to increment exit counters reason: SideExitReason, }, @@ -635,6 +636,25 @@ impl From for Target { type PosMarkerFn = Rc; +/// Cold fields of `Insn::CCall`, boxed to keep `Insn` small. The operand-bearing +/// fields (`opnds`, `stack_map`) stay inline on the variant so the operand +/// iteration macros can reach them by reference. +#[derive(Clone)] +pub struct CCallData { + /// The function pointer to be called. This should be Opnd::const_ptr + /// (Opnd::UImm) in most cases. gen_entry_trampoline() uses Opnd::Reg. + pub fptr: Opnd, + /// Optional PosMarker to remember the start address of the C call. + /// It's embedded here to insert the PosMarker after push instructions + /// that are split from this CCall during register assignment. + pub start_marker: Option, + /// Optional PosMarker to remember the end address of the C call. + /// It's embedded here to insert the PosMarker before pop instructions + /// that are split from this CCall during register assignment. + pub end_marker: Option, + pub out: Opnd, +} + /// ZJIT Low-level IR instruction #[derive(Clone)] pub enum Insn { @@ -682,19 +702,9 @@ pub enum Insn { // C function call with N arguments (variadic) CCall { opnds: Vec, - /// The function pointer to be called. This should be Opnd::const_ptr - /// (Opnd::UImm) in most cases. gen_entry_trampoline() uses Opnd::Reg. - fptr: Opnd, - /// Optional PosMarker to remember the start address of the C call. - /// It's embedded here to insert the PosMarker after push instructions - /// that are split from this CCall during register assignment. - start_marker: Option, - /// Optional PosMarker to remember the end address of the C call. - /// It's embedded here to insert the PosMarker before pop instructions - /// that are split from this CCall during register assignment. - end_marker: Option, - out: Opnd, stack_map: Option, + /// Cold fields (fptr, markers, out), boxed to keep `Insn` small. + data: Box, }, // C function return @@ -851,9 +861,9 @@ pub enum Insn { macro_rules! target_for_each_operand_impl { ($self:expr, $visit_many:ident) => { match $self { - Target::SideExit { exit: SideExit { stack, locals, .. }, .. } => { - visit_many!(stack); - visit_many!(locals); + Target::SideExit { exit, .. } => { + visit_many!(exit.stack); + visit_many!(exit.locals); } Target::Block(edge) => { visit_many!(edge.args); @@ -1086,7 +1096,6 @@ impl Insn { match self { Insn::Add { out, .. } | Insn::And { out, .. } | - Insn::CCall { out, .. } | Insn::CPop { out, .. } | Insn::CSelE { out, .. } | Insn::CSelG { out, .. } | @@ -1108,6 +1117,7 @@ impl Insn { Insn::Mul { out, .. } | Insn::URShift { out, .. } | Insn::Xor { out, .. } => Some(out), + Insn::CCall { data, .. } => Some(&data.out), _ => None } } @@ -1118,7 +1128,6 @@ impl Insn { match self { Insn::Add { out, .. } | Insn::And { out, .. } | - Insn::CCall { out, .. } | Insn::CPop { out, .. } | Insn::CSelE { out, .. } | Insn::CSelG { out, .. } | @@ -1140,6 +1149,7 @@ impl Insn { Insn::Mul { out, .. } | Insn::URShift { out, .. } | Insn::Xor { out, .. } => Some(out), + Insn::CCall { data, .. } => Some(&mut data.out), _ => None } } @@ -2283,7 +2293,8 @@ impl Assembler let mut new_ids = Vec::with_capacity(old_ids.len()); for (insn, insn_id) in old_insns.into_iter().zip(old_ids.into_iter()) { - if let Insn::CCall { opnds, out, start_marker, end_marker, fptr, stack_map } = insn { + if let Insn::CCall { opnds, stack_map, data } = insn { + let CCallData { out, start_marker, end_marker, fptr } = *data; let insn_number = insn_id.map(|id| id.0).unwrap_or(0); // Do we have a case where a ccall is emitted, but nobody // uses the result? @@ -2408,13 +2419,15 @@ impl Assembler // The CCall itself new_insns.push(Insn::CCall { - out: C_RET_OPND, opnds: vec![], // We've moved everything in to ccall regs, so this should // be empty now - start_marker: None, - end_marker: None, - fptr, stack_map: None, + data: Box::new(CCallData { + out: C_RET_OPND, + start_marker: None, + end_marker: None, + fptr, + }), }); new_ids.push(insn_id); @@ -2711,7 +2724,7 @@ impl Assembler for ((block_id, idx), target) in targets { // Compile a side exit. Note that this is past register assignment, // so you can't use an instruction that returns a VReg. - if let Target::SideExit { exit: exit @ SideExit { pc, .. }, reason } = target { + if let Target::SideExit { exit, reason } = target { // Only record the exit if `trace_side_exits` is defined and the counter is either the one specified let should_record_exit = get_option!(trace_side_exits).map(|trace| match trace { TraceExits::All => true, @@ -2755,9 +2768,9 @@ impl Assembler } else { let new_exit = self.new_label("side_exit"); self.write_label(new_exit.clone()); - asm_comment!(self, "Exit: {pc}"); + asm_comment!(self, "Exit: {}", exit.pc); compile_exit(self, &exit, None); - compiled_exits.insert(exit, new_exit.unwrap_label()); + compiled_exits.insert(*exit, new_exit.unwrap_label()); new_exit }; @@ -3439,7 +3452,7 @@ impl Assembler { let out = self.new_vreg(Opnd::match_num_bits(&opnds)); let fptr = Opnd::const_ptr(fptr); let stack_map = self.stack_map.take(); - self.push_insn(Insn::CCall { fptr, opnds, start_marker: None, end_marker: None, out, stack_map }); + self.push_insn(Insn::CCall { opnds, stack_map, data: Box::new(CCallData { fptr, start_marker: None, end_marker: None, out }) }); self.clear_stack_canary(canary_opnd); out } @@ -3449,7 +3462,7 @@ impl Assembler { pub fn ccall_into(&mut self, out: Opnd, fptr: *const u8, opnds: Vec) { let fptr = Opnd::const_ptr(fptr); let stack_map = self.stack_map.take(); - self.push_insn(Insn::CCall { fptr, opnds, start_marker: None, end_marker: None, out, stack_map }); + self.push_insn(Insn::CCall { opnds, stack_map, data: Box::new(CCallData { fptr, start_marker: None, end_marker: None, out }) }); } /// Call a C function stored in a register @@ -3457,7 +3470,7 @@ impl Assembler { assert!(matches!(fptr, Opnd::Reg(_)), "ccall_reg must be called with Opnd::Reg: {fptr:?}"); let out = self.new_vreg(num_bits); let stack_map = self.stack_map.take(); - self.push_insn(Insn::CCall { fptr, opnds: vec![], start_marker: None, end_marker: None, out, stack_map }); + self.push_insn(Insn::CCall { opnds: vec![], stack_map, data: Box::new(CCallData { fptr, start_marker: None, end_marker: None, out }) }); out } @@ -3473,12 +3486,14 @@ impl Assembler { let out = self.new_vreg(Opnd::match_num_bits(&opnds)); let stack_map = self.stack_map.take(); self.push_insn(Insn::CCall { - fptr: Opnd::const_ptr(fptr), opnds, - start_marker: Some(Rc::new(start_marker)), - end_marker: Some(Rc::new(end_marker)), - out, stack_map, + data: Box::new(CCallData { + fptr: Opnd::const_ptr(fptr), + start_marker: Some(Rc::new(start_marker)), + end_marker: Some(Rc::new(end_marker)), + out, + }), }); out } @@ -3836,6 +3851,11 @@ mod tests { Assembler::new_with_scratch_reg().1 } + #[test] + fn test_size_of_insn() { + assert_eq!(std::mem::size_of::(), 80); + } + #[test] fn test_for_each_operand() { let insn = Insn::Add { left: Opnd::None, right: Opnd::None, out: Opnd::None }; @@ -4626,11 +4646,13 @@ mod tests { let v3 = asm.new_vreg(64); asm.basic_blocks[b1.0].push_insn(Insn::CCall { opnds: vec![v2], - fptr: Opnd::UImm(0xF00), - start_marker: None, - end_marker: None, - out: v3, stack_map: None, + data: Box::new(CCallData { + fptr: Opnd::UImm(0xF00), + start_marker: None, + end_marker: None, + out: v3, + }), }); // v4 = Add(v3, v1) diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 702046058065fd..60e735131a1770 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -917,7 +917,8 @@ impl Assembler { }, // C function call - Insn::CCall { fptr, .. } => { + Insn::CCall { data, .. } => { + let fptr = &data.fptr; match fptr { Opnd::UImm(fptr) => { call_ptr(cb, RAX, *fptr as *const u8); @@ -1387,7 +1388,7 @@ mod tests { let val64 = asm.add(CFP, Opnd::UImm(64)); asm.store(Opnd::mem(64, SP, 0x10), val64); - let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, exit: SideExit { pc: 0.into(), iseq: std::ptr::null(), stack: vec![], locals: vec![], recompile: None } }; + let side_exit = Target::SideExit { reason: SideExitReason::Interrupt, exit: Box::new(SideExit { pc: 0.into(), iseq: std::ptr::null(), stack: vec![], locals: vec![], recompile: None }) }; asm.push_insn(Insn::Joz(val64, side_exit)); asm.mov(C_ARG_OPNDS[0], C_RET_OPND.with_num_bits(32)); asm.mov(C_ARG_OPNDS[1], Opnd::mem(64, SP, -8)); diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 69f71d1f13ed21..bca08013edba79 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -734,7 +734,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)), &Insn::IsBlockParamModified { flags } => gen_is_block_param_modified(asm, opnd!(flags)), &Insn::GetBlockParam { ep_offset, level, state } => gen_getblockparam(jit, asm, ep_offset, level, &function.frame_state(state)), - &Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)), + &Insn::SetLocal { val, ep_offset, level, .. } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)), Insn::GetConstant { klass, id, allow_nil, state } => gen_getconstant(jit, asm, opnd!(klass), *id, opnd!(allow_nil), &function.frame_state(*state)), Insn::GetConstantPath { ic, state } => gen_get_constant_path(jit, asm, *ic, &function.frame_state(*state)), Insn::GetClassVar { id, ic, state } => gen_getclassvar(jit, asm, *id, *ic, &function.frame_state(*state)), @@ -968,7 +968,7 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian let exit = build_side_exit(jit, state); // Let compile_exits compile a side exit. Let scratch_split lower it with split_patch_point. - asm.patch_point(Target::SideExit { exit, reason: PatchPoint(invariant) }, invariant, jit.version); + asm.patch_point(Target::SideExit { exit: Box::new(exit), reason: PatchPoint(invariant) }, invariant, jit.version); } /// This is used by scratch_split to lower PatchPoint into PadPatchPoint and PosMarker. @@ -3179,7 +3179,7 @@ fn compile_iseq(iseq: IseqPtr) -> Result { /// Build a Target::SideExit fn side_exit(jit: &JITState, state: &FrameState, reason: SideExitReason) -> Target { let exit = build_side_exit(jit, state); - Target::SideExit { exit, reason } + Target::SideExit { exit: Box::new(exit), reason } } /// Build a Target::SideExit that optionally triggers exit_recompile on the exit path. @@ -3189,7 +3189,7 @@ fn side_exit_with_recompile(jit: &JITState, state: &FrameState, reason: SideExit compiled_iseq: Opnd::Value(VALUE::from(jit.iseq())), insn_idx: state.insn_idx() as u32, }); - Target::SideExit { exit, reason } + Target::SideExit { exit: Box::new(exit), reason } } /// Build a side-exit context diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 69f79c5e853496..6e5b64f18b7711 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1015,7 +1015,7 @@ pub enum Insn { /// Get the block parameter as a Proc. GetBlockParam { level: u32, ep_offset: u32, state: InsnId }, /// Set a local variable in a higher scope or the heap - SetLocal { level: u32, ep_offset: u32, val: InsnId }, + SetLocal { level: u32, ep_offset: u32, val: InsnId, state: InsnId }, GetSpecialSymbol { symbol_type: SpecialBackrefSymbol, state: InsnId }, GetSpecialNumber { nth: u64, state: InsnId }, @@ -1604,8 +1604,8 @@ impl Insn { Ok(()) } - pub fn print<'a>(&self, ptr_map: &'a PtrPrintMap, iseq: Option) -> InsnPrinter<'a> { - InsnPrinter { inner: self.clone(), ptr_map, iseq } + pub fn print<'a>(&self, ptr_map: &'a PtrPrintMap, fun: Option<&'a Function>) -> InsnPrinter<'a> { + InsnPrinter { inner: self.clone(), ptr_map, fun } } // TODO(Jacob): Model SP. ie, all allocations modify stack size but using the effect for stack modification feels excessive @@ -1831,9 +1831,9 @@ impl Insn { /// Print adaptor for [`Insn`]. See [`PtrPrintMap`]. pub struct InsnPrinter<'a> { + fun: Option<&'a Function>, inner: Insn, ptr_map: &'a PtrPrintMap, - iseq: Option, } fn get_local_var_id(iseq: IseqPtr, level: u32, ep_offset: u32) -> ID { @@ -2174,8 +2174,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::GuardNoBitsSet { val, mask, .. } => { write!(f, "GuardNoBitsSet {val}, {}", mask.print(self.ptr_map)) }, Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), - &Insn::GetBlockParam { level, ep_offset, .. } => { - let name = get_local_var_name_for_printer(self.iseq, level, ep_offset) + &Insn::GetBlockParam { level, ep_offset, state, .. } => { + let iseq = self.fun.map(|fun| fun.frame_state(state).iseq); + let name = get_local_var_name_for_printer(iseq, level, ep_offset) .map_or(String::new(), |x| format!("{x}, ")); write!(f, "GetBlockParam {name}l{level}, EP@{ep_offset}") }, @@ -2264,8 +2265,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { &Insn::IsBlockParamModified { flags } => { write!(f, "IsBlockParamModified {flags}") }, - &Insn::SetLocal { val, level, ep_offset } => { - let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, ")); + &Insn::SetLocal { val, level, ep_offset, state } => { + let iseq = self.fun.map(|fun| fun.frame_state(state).iseq); + let name = get_local_var_name_for_printer(iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, ")); write!(f, "SetLocal {name}l{level}, EP@{ep_offset}, {val}") }, Insn::GetSpecialSymbol { symbol_type, .. } => write!(f, "GetSpecialSymbol {symbol_type:?}"), @@ -6089,7 +6091,7 @@ impl Function { }; - let opcode = insn.print(&ptr_map, Some(self.iseq)).to_string(); + let opcode = insn.print(&ptr_map, Some(self)).to_string(); // Collect inputs for a given instruction. let mut inputs = Vec::new(); @@ -6797,7 +6799,7 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> { write!(f, "{insn_id}:{} = ", insn_type.print(&self.ptr_map))?; } } - writeln!(f, "{}", insn.print(&self.ptr_map, Some(fun.iseq)))?; + writeln!(f, "{}", insn.print(&self.ptr_map, Some(fun)))?; } } Ok(()) @@ -8064,7 +8066,7 @@ fn add_iseq_to_hir( let val = state.stack_pop()?; if ep_escaped { // Write the local using EP - fun.push_insn(block, Insn::SetLocal { val, ep_offset, level: 0 }); + fun.push_insn(block, Insn::SetLocal { val, ep_offset, level: 0, state: exit_id }); } else if local_inval { // If there has been any non-leaf call since JIT entry or the last patch point, // add a patch point to make sure locals have not been escaped. @@ -8082,7 +8084,7 @@ fn add_iseq_to_hir( } YARVINSN_setlocal_WC_1 => { let ep_offset = get_arg(pc, 0).as_u32(); - fun.push_insn(block, Insn::SetLocal { val: state.stack_pop()?, ep_offset, level: 1 }); + fun.push_insn(block, Insn::SetLocal { val: state.stack_pop()?, ep_offset, level: 1, state: exit_id }); } YARVINSN_getlocal => { let ep_offset = get_arg(pc, 0).as_u32(); @@ -8103,13 +8105,13 @@ fn add_iseq_to_hir( YARVINSN_setlocal => { let ep_offset = get_arg(pc, 0).as_u32(); let level = get_arg(pc, 1).as_u32(); - fun.push_insn(block, Insn::SetLocal { val: state.stack_pop()?, ep_offset, level }); + fun.push_insn(block, Insn::SetLocal { val: state.stack_pop()?, ep_offset, level, state: exit_id }); } YARVINSN_setblockparam => { let ep_offset = get_arg(pc, 0).as_u32(); let level = get_arg(pc, 1).as_u32(); let val = state.stack_pop()?; - fun.push_insn(block, Insn::SetLocal { val, ep_offset, level }); + fun.push_insn(block, Insn::SetLocal { val, ep_offset, level, state: exit_id }); if level == 0 { state.setlocal(ep_offset, val); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 426352b75c9f82..3172864db12865 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2,9 +2,9 @@ mod hir_opt_tests { use crate::hir::*; + use crate::hir::tests::hir_build_tests::assert_contains_opcode; use crate::{hir_strings, options::*}; use insta::assert_snapshot; - use crate::hir::tests::hir_build_tests::assert_contains_opcode; #[track_caller] fn hir_string_function(function: &Function) -> String { @@ -1466,34 +1466,6 @@ mod hir_opt_tests { "); } - #[test] - fn test_optimize_top_level_call_into_send_direct() { - eval(" - def foo = [] - def test - foo - end - test; test - "); - assert_snapshot!(hir_string("test"), @" - fn test@:4: - bb1(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb3(v1) - bb2(): - EntryPoint JIT(0) - v4:BasicObject = LoadArg :self@0 - Jump bb3(v4) - bb3(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v18:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) - CheckInterrupts - Return v19 - "); - } - #[test] fn test_optimize_send_without_block_to_aliased_iseq() { eval(" @@ -1549,13 +1521,7 @@ mod hir_opt_tests { } #[test] - fn test_no_inline_nonparam_local_return() { - // Methods that return non-parameter local variables should NOT be inlined, - // because the local variable index will be out of bounds for args. - // The method must have a parameter so param_size > 0, and return a local - // that's not a parameter so local_idx >= param_size. - // Use dead code (if false) to create a local without initialization instructions, - // resulting in just getlocal + leave which enters the inlining code path. + fn test_inline_nonparam_local_return() { eval(" def foo(a) if false @@ -1580,9 +1546,11 @@ mod hir_opt_tests { v11:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v11 + v32:NilClass = Const Value(nil) + PushInlineFrame v20 (0x1038), v11 CheckInterrupts - Return v21 + PopInlineFrame + Return v32 "); } @@ -1722,7 +1690,7 @@ mod hir_opt_tests { } #[test] - fn test_optimize_private_top_level_call() { + fn test_optimize_private_call() { eval(" def foo = [] private :foo @@ -1744,14 +1712,16 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) + PushInlineFrame v18 (0x1038) + v24:ArrayExact = NewArray CheckInterrupts - Return v19 + PopInlineFrame + Return v24 "); } #[test] - fn test_optimize_top_level_call_with_overloaded_cme() { + fn test_optimize_call_with_overloaded_cme() { eval(" def test Integer(3) @@ -1772,14 +1742,16 @@ mod hir_opt_tests { v11:Fixnum[3] = Const Value(3) PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v21:BasicObject = SendDirect v20, 0x1038, :Integer (0x1048), v11 + PushInlineFrame v20 (0x1038), v11 + v27:BasicObject = InvokeBuiltin rb_f_integer1, v20, v11 CheckInterrupts - Return v21 + PopInlineFrame + Return v27 "); } #[test] - fn test_optimize_top_level_call_with_args_into_send_direct() { + fn test_optimize_call_with_args() { eval(" def foo(a, b) = [] def test @@ -1802,46 +1774,16 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 + PushInlineFrame v22 (0x1038), v11, v13 + v30:ArrayExact = NewArray CheckInterrupts - Return v23 - "); - } - - #[test] - fn test_optimize_top_level_sends_into_send_direct() { - eval(" - def foo = [] - def bar = [] - def test - foo - bar - end - test; test - "); - assert_snapshot!(hir_string("test"), @" - fn test@:5: - bb1(): - EntryPoint interpreter - v1:BasicObject = LoadSelf - Jump bb3(v1) - bb2(): - EntryPoint JIT(0) - v4:BasicObject = LoadArg :self@0 - Jump bb3(v4) - bb3(v6:BasicObject): - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v23:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048) - PatchPoint MethodRedefined(Object@0x1000, bar@0x1050, cme:0x1058) - v27:BasicObject = SendDirect v23, 0x1038, :bar (0x1048) - CheckInterrupts - Return v27 + PopInlineFrame + Return v30 "); } #[test] - fn test_optimize_send_direct_no_optionals_passed() { + fn test_optimize_send_no_optionals_passed() { eval(" def foo(a=1, b=2) = a + b def test = foo @@ -1860,14 +1802,19 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) + PushInlineFrame v18 (0x1038) + v25:Fixnum[1] = Const Value(1) + v33:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) + v60:Fixnum[3] = Const Value(3) CheckInterrupts - Return v19 + PopInlineFrame + Return v60 "); } #[test] - fn test_optimize_send_direct_one_optional_passed() { + fn test_optimize_send_one_optional_passed() { eval(" def foo(a=1, b=2) = a + b def test = foo 3 @@ -1887,14 +1834,18 @@ mod hir_opt_tests { v11:Fixnum[3] = Const Value(3) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v11 + PushInlineFrame v20 (0x1038), v11 + v35:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) + v61:Fixnum[5] = Const Value(5) CheckInterrupts - Return v21 + PopInlineFrame + Return v61 "); } #[test] - fn test_optimize_send_direct_all_optionals_passed() { + fn test_optimize_send_all_optionals_passed() { eval(" def foo(a=1, b=2) = a + b def test = foo 3, 4 @@ -1915,9 +1866,12 @@ mod hir_opt_tests { v13:Fixnum[4] = Const Value(4) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 + PushInlineFrame v22 (0x1038), v11, v13 + PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) + v62:Fixnum[7] = Const Value(7) CheckInterrupts - Return v23 + PopInlineFrame + Return v62 "); } @@ -1942,19 +1896,28 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v44:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v45:BasicObject = SendDirect v44, 0x1038, :target (0x1048) + PushInlineFrame v44 (0x1038) + v56:Fixnum[1] = Const Value(1) + v66:Fixnum[2] = Const Value(2) + v76:Fixnum[3] = Const Value(3) + v86:Fixnum[4] = Const Value(4) + v101:ArrayExact = NewArray v56, v66, v76, v86 + CheckInterrupts + PopInlineFrame v14:Fixnum[10] = Const Value(10) v16:Fixnum[20] = Const Value(20) v18:Fixnum[30] = Const Value(30) - PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) - v48:BasicObject = SendDirect v44, 0x1038, :target (0x1048), v14, v16, v18 + PushInlineFrame v44 (0x1038), v14, v16, v18 + v151:Fixnum[4] = Const Value(4) + v166:ArrayExact = NewArray v14, v16, v18, v151 + PopInlineFrame v24:Fixnum[10] = Const Value(10) v26:Fixnum[20] = Const Value(20) v28:Fixnum[30] = Const Value(30) v30:Fixnum[40] = Const Value(40) v32:Fixnum[50] = Const Value(50) v34:BasicObject = Send v44, :target, v24, v26, v28, v30, v32 # SendFallbackReason: Argument count does not match parameter count - v37:ArrayExact = NewArray v45, v48, v34 + v37:ArrayExact = NewArray v101, v166, v34 CheckInterrupts Return v37 "); @@ -3839,7 +3802,7 @@ mod hir_opt_tests { } #[test] - fn test_send_direct_to_instance_method() { + fn test_send_to_instance_method() { eval(" class C def foo = [] @@ -3868,14 +3831,16 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] recompile - v24:BasicObject = SendDirect v23, 0x1040, :foo (0x1050) + PushInlineFrame v23 (0x1040) + v29:ArrayExact = NewArray CheckInterrupts - Return v24 + PopInlineFrame + Return v29 "); } #[test] - fn test_send_direct_iseq_with_block() { + fn test_send_iseq_with_block() { let result = eval(" def foo(a, b, &block) = block.call(a, b) def test = foo(1, 2) { |a, b| a + b } @@ -3898,9 +3863,25 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 + v51:NilClass = Const Value(nil) + PushInlineFrame v22 (0x1038), v11, v13 + v33:CPtr = GetEP 0 + v34:CUInt64 = LoadField v33, :VM_ENV_DATA_INDEX_FLAGS@0x1040 + v35:CBool = IsBlockParamModified v34 + CondBranch v35, bb6(), bb7() + bb6(): + v37:BasicObject = LoadField v33, :block@0x1041 + Jump bb8(v37, v37) + bb7(): + v39:CInt64 = LoadField v33, :VM_ENV_DATA_INDEX_SPECVAL@0x1042 + v40:CInt64 = GuardAnyBitSet v39, CUInt64(1) recompile + v41:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1048)) + Jump bb8(v41, v51) + bb8(v31:BasicObject, v32:BasicObject): + v46:BasicObject = Send v31, :call, v11, v13 # SendFallbackReason: SendWithoutBlock: unsupported optimized method type BlockCall CheckInterrupts - Return v23 + PopInlineFrame + Return v46 "); } @@ -4039,9 +4020,11 @@ mod hir_opt_tests { v11:Fixnum[10] = Const Value(10) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v11 + PushInlineFrame v20 (0x1038), v11 + v27:Fixnum[80] = Const Value(80) CheckInterrupts - Return v21 + PopInlineFrame + Return v11 "); } @@ -4069,9 +4052,13 @@ mod hir_opt_tests { v13:Fixnum[20] = Const Value(20) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 + PushInlineFrame v22 (0x1038), v11, v13 + v30:Fixnum[80] = Const Value(80) + PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) + v66:Fixnum[110] = Const Value(110) CheckInterrupts - Return v23 + PopInlineFrame + Return v66 "); } @@ -4098,9 +4085,12 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 + v38:Fixnum[0] = Const Value(0) + PushInlineFrame v22 (0x1038), v11, v13 + v33:ArrayExact = NewArray v11, v13 CheckInterrupts - Return v23 + PopInlineFrame + Return v33 "); } @@ -4128,9 +4118,12 @@ mod hir_opt_tests { v15:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v25:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v26:BasicObject = SendDirect v25, 0x1038, :foo (0x1048), v13, v15, v11 + v43:Fixnum[0] = Const Value(0) + PushInlineFrame v25 (0x1038), v13, v15, v11 + v38:ArrayExact = NewArray v13, v15, v11 CheckInterrupts - Return v26 + PopInlineFrame + Return v38 "); } @@ -4158,9 +4151,12 @@ mod hir_opt_tests { v15:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v25:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v26:BasicObject = SendDirect v25, 0x1038, :foo (0x1048), v11, v15, v13 + v43:Fixnum[0] = Const Value(0) + PushInlineFrame v25 (0x1038), v11, v15, v13 + v38:ArrayExact = NewArray v11, v15, v13 CheckInterrupts - Return v26 + PopInlineFrame + Return v38 "); } @@ -4187,9 +4183,12 @@ mod hir_opt_tests { v13:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048), v11, v13 + v38:Fixnum[0] = Const Value(0) + PushInlineFrame v22 (0x1038), v11, v13 + v33:ArrayExact = NewArray v11, v13 CheckInterrupts - Return v23 + PopInlineFrame + Return v33 "); } @@ -4217,15 +4216,21 @@ mod hir_opt_tests { v15:Fixnum[4] = Const Value(4) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v37:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v38:BasicObject = SendDirect v37, 0x1038, :foo (0x1048), v11, v13, v15 + v71:Fixnum[0] = Const Value(0) + PushInlineFrame v37 (0x1038), v11, v13, v15 + v51:Fixnum[2] = Const Value(2) + v65:ArrayExact = NewArray v51, v13 + CheckInterrupts + PopInlineFrame v20:Fixnum[1] = Const Value(1) v22:Fixnum[2] = Const Value(2) v24:Fixnum[4] = Const Value(4) v26:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v42:BasicObject = SendDirect v37, 0x1038, :foo (0x1048), v20, v22, v26, v24 - v30:ArrayExact = NewArray v38, v42 - CheckInterrupts + v103:Fixnum[0] = Const Value(0) + PushInlineFrame v37 (0x1038), v20, v22, v26, v24 + v98:ArrayExact = NewArray v22, v26 + PopInlineFrame + v30:ArrayExact = NewArray v65, v98 Return v30 "); } @@ -4254,15 +4259,21 @@ mod hir_opt_tests { v34:Fixnum[4] = Const Value(4) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v37:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v38:BasicObject = SendDirect v37, 0x1038, :foo (0x1048), v11, v13, v34 + v73:Fixnum[0] = Const Value(0) + PushInlineFrame v37 (0x1038), v11, v13, v34 + v51:Fixnum[2] = Const Value(2) + v67:ArrayExact = NewArray v11, v51, v13, v34 + CheckInterrupts + PopInlineFrame v18:Fixnum[1] = Const Value(1) v20:Fixnum[2] = Const Value(2) v22:Fixnum[40] = Const Value(40) v24:Fixnum[30] = Const Value(30) - PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) - v42:BasicObject = SendDirect v37, 0x1038, :foo (0x1048), v18, v20, v24, v22 - v28:ArrayExact = NewArray v38, v42 - CheckInterrupts + v107:Fixnum[0] = Const Value(0) + PushInlineFrame v37 (0x1038), v18, v20, v24, v22 + v102:ArrayExact = NewArray v18, v20, v24, v22 + PopInlineFrame + v28:ArrayExact = NewArray v67, v102 Return v28 "); } @@ -4331,9 +4342,11 @@ mod hir_opt_tests { v11:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v11 + v32:Fixnum[0] = Const Value(0) + PushInlineFrame v20 (0x1038), v11 CheckInterrupts - Return v21 + PopInlineFrame + Return v11 "); } @@ -4446,9 +4459,14 @@ mod hir_opt_tests { v17:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v21:BasicObject = SendDirect v20, 0x1038, :foo (0x1048), v17 + v37:Fixnum[0] = Const Value(0) + PushInlineFrame v20 (0x1038), v17 + v29:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1040, +@0x1048, cme:0x1050) + v46:Fixnum[2] = Const Value(2) CheckInterrupts - Return v21 + PopInlineFrame + Return v46 "); } @@ -4748,8 +4766,15 @@ mod hir_opt_tests { v48:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) - v52:BasicObject = SendDirect v48, 0x1068, :initialize (0x1078), v17 + PushInlineFrame v48 (0x1068), v17 + v62:CShape = LoadField v48, :shape_id@0x1070 + v63:CShape[0x1071] = GuardBitEquals v62, CShape(0x1071) recompile + StoreField v48, :@x@0x1072, v17 + WriteBarrier v48, v17 + v66:CShape[0x1073] = Const CShape(0x1073) + StoreField v48, :shape_id@0x1070, v66 CheckInterrupts + PopInlineFrame Return v48 "); } @@ -4842,8 +4867,24 @@ mod hir_opt_tests { v46:Fixnum[0] = Const Value(0) PatchPoint NoSingletonClass(Hash@0x1008) PatchPoint MethodRedefined(Hash@0x1008, initialize@0x1038, cme:0x1040) - v51:BasicObject = SendDirect v45, 0x1068, :initialize (0x1078), v46 + v94:Fixnum[0] = Const Value(0) + v95:NilClass = Const Value(nil) + PushInlineFrame v45 (0x1068), v46 + v61:TrueClass = Const Value(true) + v79:CPtr = GetEP 0 + v80:CUInt64 = LoadField v79, :VM_ENV_DATA_INDEX_FLAGS@0x1070 + v81:CBool = IsBlockParamModified v80 + CondBranch v81, bb10(), bb11() + bb10(): + v83:BasicObject = LoadField v79, :block@0x1071 + Jump bb12(v83) + bb11(): + v85:BasicObject = GetBlockParam :block, l0, EP@4 + Jump bb12(v85) + bb12(v78:BasicObject): + v88:BasicObject = InvokeBuiltin rb_hash_init, v45, v46, v61, v61, v78 CheckInterrupts + PopInlineFrame Return v45 "); assert_snapshot!(inspect("test"), @"{}"); @@ -7330,9 +7371,11 @@ mod hir_opt_tests { v13:Fixnum[10] = Const Value(10) PatchPoint NoSingletonClass(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) - v24:BasicObject = SendDirect v11, 0x1040, :[] (0x1050), v13 + PushInlineFrame v11 (0x1040), v13 + v30:ArrayExact = NewArray CheckInterrupts - Return v24 + PopInlineFrame + Return v30 "); } @@ -7390,9 +7433,11 @@ mod hir_opt_tests { v11:ArrayExact = ArrayDup v10 PatchPoint NoSingletonClass(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) - v21:BasicObject = SendDirect v11, 0x1040, :max (0x1050) + PushInlineFrame v11 (0x1040) + v26:ArrayExact = NewArray CheckInterrupts - Return v21 + PopInlineFrame + Return v26 "); } @@ -9593,7 +9638,7 @@ mod hir_opt_tests { } #[test] - fn test_send_direct_iseq_with_block_no_callee_block_param() { + fn test_send_iseq_with_block_no_callee_block_param() { let result = eval(r#" def foo yield 1 @@ -9616,9 +9661,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) + PushInlineFrame v18 (0x1038) + v24:Fixnum[1] = Const Value(1) + v26:BasicObject = InvokeBlock v24 # SendFallbackReason: InvokeBlock: not yet specialized CheckInterrupts - Return v19 + PopInlineFrame + Return v26 "); } @@ -9646,9 +9694,49 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v18:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v19:BasicObject = SendDirect v18, 0x1038, :foo (0x1048) + v71:NilClass = Const Value(nil) + PushInlineFrame v18 (0x1038) + v27:CPtr = GetEP 0 + v28:CUInt64 = LoadField v27, :VM_ENV_DATA_INDEX_FLAGS@0x1040 + v29:CBool = IsBlockParamModified v28 + CondBranch v29, bb7(), bb8() + bb7(): + v31:BasicObject = LoadField v27, :blk@0x1041 + Jump bb9(v31, v31) + bb8(): + v33:CInt64 = LoadField v27, :VM_ENV_DATA_INDEX_SPECVAL@0x1042 + v34:CInt64[0] = GuardBitEquals v33, CInt64(0) recompile + v35:NilClass = Const Value(nil) + Jump bb9(v35, v71) + bb9(v25:BasicObject, v26:BasicObject): CheckInterrupts - Return v19 + v39:CBool = Test v25 + CondBranch v39, bb10(), bb6(v18, v26) + bb10(): + v46:CPtr = GetEP 0 + v47:CUInt64 = LoadField v46, :VM_ENV_DATA_INDEX_FLAGS@0x1040 + v48:CBool = IsBlockParamModified v47 + CondBranch v48, bb11(), bb12() + bb11(): + v50:BasicObject = LoadField v46, :blk@0x1041 + Jump bb13(v50, v50) + bb12(): + v52:CInt64 = LoadField v46, :VM_ENV_DATA_INDEX_SPECVAL@0x1042 + v53:CInt64 = GuardAnyBitSet v52, CUInt64(1) recompile + v54:ObjectSubclass[BlockParamProxy] = Const Value(VALUE(0x1048)) + Jump bb13(v54, v26) + bb13(v44:BasicObject, v45:BasicObject): + v57:BasicObject = Send v44, :call # SendFallbackReason: SendWithoutBlock: no profile data available + CheckInterrupts + Jump bb4(v57) + bb6(v62:ObjectSubclass[class_exact*:Object@VALUE(0x1000)], v63:BasicObject): + v66:Fixnum[42] = Const Value(42) + CheckInterrupts + Jump bb4(v66) + bb4(v72:BasicObject): + PopInlineFrame + CheckInterrupts + Return v72 "); } @@ -9677,9 +9765,12 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v19:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile - v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) + v48:NilClass = Const Value(nil) + PushInlineFrame v19 (0x1038) CheckInterrupts - Return v20 + v43:Fixnum[42] = Const Value(42) + PopInlineFrame + Return v43 "); } @@ -15417,7 +15508,7 @@ mod hir_opt_tests { } #[test] - fn test_invokesuper_to_iseq_optimizes_to_direct() { + fn test_invokesuper_to_iseq_optimizes() { eval(" class A def foo @@ -15437,7 +15528,6 @@ mod hir_opt_tests { // A Ruby method as the target of `super` should optimize provided no block is given. let hir = hir_string_proc("B.new.method(:foo)"); assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendDirect but got:\n{hir}"); - assert!(hir.contains("SendDirect"), "Should optimize to SendDirect for call without args or block:\n{hir}"); assert_snapshot!(hir, @" fn foo@:10: @@ -15456,9 +15546,12 @@ mod hir_opt_tests { v20:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v19, Value(VALUE(0x1040)) v21:RubyValue = LoadField v18, :VM_ENV_DATA_INDEX_SPECVAL@0x1048 v22:FalseClass = GuardBitEquals v21, Value(false) - v23:BasicObject = SendDirect v6, 0x1050, :foo (0x1060) + PushInlineFrame v6 (0x1050) + v28:StringExact[VALUE(0x1058)] = Const Value(VALUE(0x1058)) + v29:StringExact = StringCopy v28 CheckInterrupts - Return v23 + PopInlineFrame + Return v29 "); } @@ -15487,7 +15580,7 @@ mod hir_opt_tests { } #[test] - fn test_invokesuper_with_positional_args_optimizes_to_direct() { + fn test_invokesuper_with_positional_args_optimizes() { eval(" class A def foo(x) @@ -15506,7 +15599,6 @@ mod hir_opt_tests { let hir = hir_string_proc("B.new.method(:foo)"); assert!(!hir.contains("InvokeSuper "), "InvokeSuper should optimize to SendDirect but got:\n{hir}"); - assert!(hir.contains("SendDirect"), "Should optimize to SendDirect for call without args or block:\n{hir}"); assert_snapshot!(hir, @" fn foo@:10: @@ -15528,13 +15620,17 @@ mod hir_opt_tests { v30:CallableMethodEntry[VALUE(0x1048)] = GuardBitEquals v29, Value(VALUE(0x1048)) v31:RubyValue = LoadField v28, :VM_ENV_DATA_INDEX_SPECVAL@0x1050 v32:FalseClass = GuardBitEquals v31, Value(false) - v33:BasicObject = SendDirect v9, 0x1058, :foo (0x1068), v10 - v18:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1070, +@0x1078, cme:0x1080) - v36:Fixnum = GuardType v33, Fixnum recompile - v37:Fixnum = FixnumAdd v36, v18 + PushInlineFrame v9 (0x1058), v10 + v40:Fixnum[2] = Const Value(2) + PatchPoint MethodRedefined(Integer@0x1060, *@0x1068, cme:0x1070) + v54:Fixnum = GuardType v10, Fixnum recompile + v55:Fixnum = FixnumMult v54, v40 CheckInterrupts - Return v37 + PopInlineFrame + v18:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1060, +@0x1098, cme:0x10a0) + v59:Fixnum = FixnumAdd v55, v18 + Return v59 "); } @@ -16856,7 +16952,7 @@ mod hir_opt_tests { // After profiling via the side exit, rebuilding HIR should now // have a SendDirect for greet_recompile instead of SideExit. - assert_snapshot!(hir_string("test_no_profile_recompile"), @r" + assert_snapshot!(hir_string("test_no_profile_recompile"), @" fn test_no_profile_recompile@:4: bb1(): EntryPoint interpreter @@ -16879,11 +16975,14 @@ mod hir_opt_tests { v23:Fixnum[42] = Const Value(42) PatchPoint MethodRedefined(Object@0x1008, greet_recompile@0x1010, cme:0x1018) v43:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile - v44:BasicObject = SendDirect v43, 0x1040, :greet_recompile (0x1050), v23 + PushInlineFrame v43 (0x1040), v23 + PatchPoint MethodRedefined(Integer@0x1048, to_s@0x1050, cme:0x1058) + v63:StringExact = CCallVariadic v23, :Integer#to_s@0x1080 CheckInterrupts - Return v44 + PopInlineFrame + Return v63 bb4(v30:BasicObject, v31:Falsy): - v35:StringExact[VALUE(0x1058)] = Const Value(VALUE(0x1058)) + v35:StringExact[VALUE(0x1088)] = Const Value(VALUE(0x1088)) v36:StringExact = StringCopy v35 CheckInterrupts Return v36 @@ -16968,6 +17067,8 @@ mod hir_opt_tests { // the HIR. The auto-compile creates version 1, and hir_string() creates version 2 // (= MAX_ISEQ_VERSIONS), so this is the final version. set_call_threshold(3); + set_inline_threshold(0); + eval(" def greet_final(x) = x.to_s def test_final_version(flag) @@ -16983,7 +17084,7 @@ mod hir_opt_tests { eval("3.times { test_final_version(false) }"); // On the final version, greet_final should be a Send fallback, not a SideExit. - assert_snapshot!(hir_string("test_final_version"), @r" + assert_snapshot!(hir_string("test_final_version"), @" fn test_final_version@:4: bb1(): EntryPoint interpreter @@ -17833,6 +17934,7 @@ mod hir_opt_tests { #[test] fn test_trigger_guard_type_recompilation() { + set_inline_threshold(0); eval(" class C def f(x) @@ -17864,7 +17966,6 @@ mod hir_opt_tests { # Do this enough times to cause a recompilation num_to_compile.times { c.f(1.5) } "); - let final_hir = hir_string_proc("C.new.method(:f)"); assert_snapshot!(format!("{intermediate_hir}\n{final_hir}"), @" diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 8289a43d7f924e..770dfc271edf5a 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -107,7 +107,7 @@ mod snapshot_tests { } #[test] - fn test_send_direct_with_reordered_kwargs_has_snapshot() { + fn test_send_with_reordered_kwargs_has_snapshot() { eval(" def foo(a:, b:, c:) = [a, b, c] def test = foo(c: 3, a: 1, b: 2) @@ -136,16 +136,19 @@ mod snapshot_tests { v23:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v13, v15, v11], locals: [] } PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) v25:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile - v26:BasicObject = SendDirect v25, 0x1048, :foo (0x1058), v13, v15, v11 - v18:Any = Snapshot FrameState { pc: 0x1060, stack: [v26], locals: [] } - PatchPoint NoTracePoint + v43:Fixnum[0] = Const Value(0) + PushInlineFrame v25 (0x1048), v13, v15, v11 + v37:Any = Snapshot FrameState { pc: 0x1050, stack: [v13, v15, v11], locals: [a=v13, b=v15, c=v11, ID(0)=v43], caller: v23 } + v38:ArrayExact = NewArray v13, v15, v11 + v39:Any = Snapshot FrameState { pc: 0x1058, stack: [v38], locals: [a=v13, b=v15, c=v11, ID(0)=v43], caller: v23 } CheckInterrupts - Return v26 + PopInlineFrame + Return v38 "); } #[test] - fn test_send_direct_with_kwargs_in_order_has_snapshot() { + fn test_send_with_kwargs_in_order_has_snapshot() { eval(" def foo(a:, b:) = [a, b] def test = foo(a: 1, b: 2) @@ -172,11 +175,14 @@ mod snapshot_tests { v14:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v11, v13], locals: [] } PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) v22:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile - v23:BasicObject = SendDirect v22, 0x1048, :foo (0x1058), v11, v13 - v16:Any = Snapshot FrameState { pc: 0x1060, stack: [v23], locals: [] } - PatchPoint NoTracePoint + v38:Fixnum[0] = Const Value(0) + PushInlineFrame v22 (0x1048), v11, v13 + v32:Any = Snapshot FrameState { pc: 0x1050, stack: [v11, v13], locals: [a=v11, b=v13, ID(0)=v38], caller: v14 } + v33:ArrayExact = NewArray v11, v13 + v34:Any = Snapshot FrameState { pc: 0x1058, stack: [v33], locals: [a=v11, b=v13, ID(0)=v38], caller: v14 } CheckInterrupts - Return v23 + PopInlineFrame + Return v33 "); } diff --git a/zjit/src/options.rs b/zjit/src/options.rs index bf09235002dd8d..fecc0fc3977549 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -25,11 +25,15 @@ pub const DEFAULT_CALL_THRESHOLD: CallThreshold = 30; pub type CallThreshold = u64; /// Default --zjit-inline-threshold -pub const DEFAULT_INLINE_THRESHOLD: InlineThreshold = 0; +/// TODO (nirvdrum 2026-06-25): 30 has proven to work well with ruby-bench, but we should finely +/// tune across more workloads. +pub const DEFAULT_INLINE_THRESHOLD: InlineThreshold = 30; pub type InlineThreshold = usize; /// Default --zjit-inline-budget -pub const DEFAULT_INLINE_BUDGET: InlineBudget = 500; +/// TODO (nirvdrum 2026-06-25): 200 has proven to strike a good balance between memory usage and +/// run time performance on ruby-bench, but we should finely tune across more workloads. +pub const DEFAULT_INLINE_BUDGET: InlineBudget = 200; pub const INLINE_BUDGET_UNLIMITED: InlineBudget = 0; pub type InlineBudget = usize; @@ -634,7 +638,6 @@ pub fn set_call_threshold(call_threshold: CallThreshold) { update_profile_threshold(); } -/// Update --zjit-inline-threshold for testing #[cfg(test)] pub fn set_inline_threshold(inline_threshold: InlineThreshold) { rb_zjit_prepare_options();