Skip to content
Merged
13 changes: 13 additions & 0 deletions .github/workflows/mingw.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ jobs:
with:
ruby-version: '3.2'

- name: Remove Strawberry Perl pkg-config
working-directory:
# `pkg-config.bat` included in Strawberry Perl is written in
# Perl and doesn't work when another msys2 `perl` precede its
# own `perl`.
#
# ```
# Can't find C:\Strawberry\perl\bin\pkg-config.bat on PATH, '.' not in PATH.
# ```
run: |
Get-Command pkg-config.bat | % { ren $_.path ($_.path + "~") }
shell: pwsh

- name: Misc system & package info
working-directory:
run: |
Expand Down
10 changes: 5 additions & 5 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1755,7 +1755,6 @@ rb_gc_pointer_to_heap_p(VALUE obj)
#define LAST_OBJECT_ID() (object_id_counter * OBJ_ID_INCREMENT)
static VALUE id2ref_value = 0;
static st_table *id2ref_tbl = NULL;
static bool id2ref_tbl_built = false;

#if SIZEOF_SIZE_T == SIZEOF_LONG_LONG
static size_t object_id_counter = 1;
Expand Down Expand Up @@ -1947,6 +1946,7 @@ build_id2ref_i(VALUE obj, void *data)
case T_CLASS:
case T_MODULE:
if (RCLASS(obj)->object_id) {
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
st_insert(id2ref_tbl, RCLASS(obj)->object_id, obj);
}
break;
Expand All @@ -1955,6 +1955,7 @@ build_id2ref_i(VALUE obj, void *data)
break;
default:
if (rb_shape_obj_has_id(obj)) {
RUBY_ASSERT(!rb_objspace_garbage_object_p(obj));
st_insert(id2ref_tbl, rb_obj_id(obj), obj);
}
break;
Expand All @@ -1979,12 +1980,12 @@ object_id_to_ref(void *objspace_ptr, VALUE object_id)

// build_id2ref_i will most certainly malloc, which could trigger GC and sweep
// objects we just added to the table.
bool gc_disabled = RTEST(rb_gc_disable_no_rest());
// By calling rb_gc_disable() we also save having to handle potentially garbage objects.
bool gc_disabled = RTEST(rb_gc_disable());
{
rb_gc_impl_each_object(objspace, build_id2ref_i, (void *)id2ref_tbl);
}
if (!gc_disabled) rb_gc_enable();
id2ref_tbl_built = true;
}

VALUE obj;
Expand Down Expand Up @@ -2036,10 +2037,9 @@ obj_free_object_id(VALUE obj)
RUBY_ASSERT(FIXNUM_P(obj_id) || RB_TYPE_P(obj_id, T_BIGNUM));

if (!st_delete(id2ref_tbl, (st_data_t *)&obj_id, NULL)) {
// If we're currently building the table then it's not a bug.
// The the object is a T_IMEMO/fields, then it's possible the actual object
// has been garbage collected already.
if (id2ref_tbl_built && !RB_TYPE_P(obj, T_IMEMO)) {
if (!RB_TYPE_P(obj, T_IMEMO)) {
rb_bug("Object ID seen, but not in _id2ref table: object_id=%llu object=%s", NUM2ULL(obj_id), rb_obj_info(obj));
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/mkmf/test_pkg_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def setup
Cflags: -I${includedir}/cflags-I --cflags-other
EOF

@pkg_config_path, ENV["PKG_CONFIG_PATH"] = ENV["PKG_CONFIG_PATH"], mkintpath(@fixtures_dir)
@pkg_config_path, ENV["PKG_CONFIG_PATH"] = ENV["PKG_CONFIG_PATH"], @fixtures_dir
end
end

Expand Down
12 changes: 12 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,18 @@ def test
}, call_threshold: 2
end

def test_send_on_heap_object_in_spilled_arg
# This leads to a register spill, so not using `assert_compiles`
assert_runs 'Hash', %q{
def entry(a1, a2, a3, a4, a5, a6, a7, a8, a9)
a9.itself.class
end

entry(1, 2, 3, 4, 5, 6, 7, 8, {}) # profile
entry(1, 2, 3, 4, 5, 6, 7, 8, {})
}, call_threshold: 2
end

def test_invokebuiltin
omit 'Test fails at the moment due to not handling optional parameters'
assert_compiles '["."]', %q{
Expand Down
61 changes: 49 additions & 12 deletions tool/zjit_bisect.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
#!/usr/bin/env ruby
require 'logger'
require 'open3'
require 'optparse'
require 'shellwords'
require 'tempfile'
require 'timeout'

RUBY = ARGV[0] || raise("Usage: ruby jit_bisect.rb <path_to_ruby> <options>")
OPTIONS = ARGV[1] || raise("Usage: ruby jit_bisect.rb <path_to_ruby> <options>")
TIMEOUT_SEC = 5
ARGS = {timeout: 5}
OptionParser.new do |opts|
opts.banner += " <path_to_ruby> -- <options>"
opts.on("--timeout=TIMEOUT_SEC", "Seconds until child process is killed") do |timeout|
ARGS[:timeout] = Integer(timeout)
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!

RUBY = ARGV[0] || raise("Usage: ruby jit_bisect.rb <path_to_ruby> -- <options>")
OPTIONS = ARGV[1..]
raise("Usage: ruby jit_bisect.rb <path_to_ruby> -- <options>") if OPTIONS.empty?
LOGGER = Logger.new($stdout)

# From https://github.com/tekknolagi/omegastar
Expand Down Expand Up @@ -58,40 +72,63 @@ def run_bisect(command, items)
bisect_impl(command, [], items)
end

def run_ruby *cmd
stdout_data = nil
stderr_data = nil
status = nil
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
pid = wait_thr.pid
begin
Timeout.timeout(ARGS[:timeout]) do
stdout_data = stdout.read
stderr_data = stderr.read
status = wait_thr.value
end
rescue Timeout::Error
Process.kill("KILL", pid)
stderr_data = "(killed due to timeout)"
# Wait for the process to be reaped
wait_thr.value
status = 1
end
end
[stdout_data, stderr_data, status]
end

def run_with_jit_list(ruby, options, jit_list)
# Make a new temporary file containing the JIT list
Tempfile.create("jit_list") do |temp_file|
temp_file.write(jit_list.join("\n"))
temp_file.flush
temp_file.close
# Run the JIT with the temporary file
Open3.capture3("#{ruby} --zjit-allowed-iseqs=#{temp_file.path} #{options}")
run_ruby ruby, "--zjit-allowed-iseqs=#{temp_file.path}", *options
end
end

# Try running with no JIT list to get a stable baseline
_, stderr, status = run_with_jit_list(RUBY, OPTIONS, [])
if !status.success?
_, stderr, exitcode = run_with_jit_list(RUBY, OPTIONS, [])
if exitcode != 0
raise "Command failed with empty JIT list: #{stderr}"
end
# Collect the JIT list from the failing Ruby process
jit_list = nil
Tempfile.create "jit_list" do |temp_file|
Open3.capture3("#{RUBY} --zjit-log-compiled-iseqs=#{temp_file.path} #{OPTIONS}")
run_ruby RUBY, "--zjit-log-compiled-iseqs=#{temp_file.path}", *OPTIONS
jit_list = File.readlines(temp_file.path).map(&:strip).reject(&:empty?)
end
LOGGER.info("Starting with JIT list of #{jit_list.length} items.")
# Now narrow it down
command = lambda do |items|
status = Timeout.timeout(TIMEOUT_SEC) do
_, _, status = run_with_jit_list(RUBY, OPTIONS, items)
status
end
status.success?
_, _, exitcode = run_with_jit_list(RUBY, OPTIONS, items)
exitcode == 0
end
result = run_bisect(command, jit_list)
File.open("jitlist.txt", "w") do |file|
file.puts(result)
end
puts "Run:"
command = [RUBY, "--zjit-allowed-iseqs=jitlist.txt", *OPTIONS].shelljoin
puts command
puts "Reduced JIT list (available in jitlist.txt):"
puts result
2 changes: 1 addition & 1 deletion zjit/src/backend/lir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl Opnd
})
},

_ => unreachable!("memory operand with non-register base")
_ => unreachable!("memory operand with non-register base: {base:?}")
}
}

Expand Down
8 changes: 7 additions & 1 deletion zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,9 +1088,15 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd, guard
} else if let Some(expected_class) = guard_type.runtime_exact_ruby_class() {
asm_comment!(asm, "guard exact class for non-immediate types");

let side_exit = side_exit(jit, state, GuardType(guard_type))?;
// If val isn't in a register, load it to use it as the base of Opnd::mem later.
// TODO: Max thinks codegen should not care about the shapes of the operands except to create them. (Shopify/ruby#685)
let val = match val {
Opnd::Reg(_) | Opnd::VReg { .. } => val,
_ => asm.load(val),
};

// Check if it's a special constant
let side_exit = side_exit(jit, state, GuardType(guard_type))?;
asm.test(val, (RUBY_IMMEDIATE_MASK as u64).into());
asm.jnz(side_exit.clone());

Expand Down
Loading