diff options
Diffstat (limited to 'zjit/src/codegen_tests.rs')
| -rw-r--r-- | zjit/src/codegen_tests.rs | 5714 |
1 files changed, 5714 insertions, 0 deletions
diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs new file mode 100644 index 0000000000..b099371718 --- /dev/null +++ b/zjit/src/codegen_tests.rs @@ -0,0 +1,5714 @@ +#![cfg(test)] + +use super::{gen_insn, JITState}; +use crate::asm::CodeBlock; +use crate::backend::lir::Assembler; +use crate::codegen::max_iseq_versions; +use crate::cruby::*; +use crate::hir::{Insn, iseq_to_hir}; +use crate::options::{rb_zjit_prepare_options, set_call_threshold}; +use crate::payload::IseqVersion; +use crate::hir::tests::hir_build_tests::assert_contains_opcode; +use crate::payload::*; +use insta::assert_snapshot; + +#[test] +fn test_breakpoint_hir_codegen() { + rb_zjit_prepare_options(); + + eval("def test_breakpoint_hir_codegen = nil"); + let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("self", "test_breakpoint_hir_codegen")); + unsafe { crate::cruby::rb_zjit_profile_disable(iseq) }; + let mut function = iseq_to_hir(iseq).unwrap(); + let breakpoint = function.push_insn(function.entries_block, Insn::BreakPoint); + + let mut jit = JITState::new( + IseqVersion::new(iseq), + function.num_insns(), + function.num_blocks(), + 0, + ); + let mut asm = Assembler::new(); + asm.new_block_without_id("test"); + let mut cb = CodeBlock::new_dummy(); + + gen_insn(&mut cb, &mut jit, &mut asm, &function, breakpoint, &function.find(breakpoint)).unwrap(); + asm.compile_with_num_regs(&mut cb, 0); + + #[cfg(target_arch = "x86_64")] + assert_eq!(cb.hexdump(), "cc"); + + #[cfg(target_arch = "aarch64")] + assert_eq!(cb.hexdump(), "00003ed4"); +} + +#[test] +fn test_call_itself() { + assert_snapshot!(inspect(" + def test = 42.itself + test + test + "), @"42"); +} + +#[test] +fn test_nil() { + assert_snapshot!(inspect(" + def test = nil + test + test + "), @"nil"); +} + +#[test] +fn test_putobject() { + assert_snapshot!(inspect(" + def test = 1 + test + test + "), @"1"); +} + +#[test] +fn test_dupstring() { + eval(r##" + def test = "#{""}" + test + "##); + assert_contains_opcode("test", YARVINSN_dupstring); + assert_snapshot!(assert_compiles(r##"test"##), @r#""""#); +} + +#[test] +fn test_dupchilledstring() { + eval(r#" + def test = "" + test + "#); + assert_contains_opcode("test", YARVINSN_dupchilledstring); + assert_snapshot!(assert_compiles(r#"test"#), @r#""""#); +} + +#[test] +fn test_leave_param() { + assert_snapshot!(inspect(" + def test(n) = n + test(5) + test(5) + "), @"5"); +} + +#[test] +fn test_getglobal_with_warning() { + eval(r#" + Warning[:deprecated] = true + + module Warning + def warn(message) + raise + end + end + + def test + $= + rescue + "rescued" + end + $VERBOSE = true + test + "#); + assert_contains_opcode("test", YARVINSN_getglobal); + assert_snapshot!(assert_compiles(r#"test"#), @r#""rescued""#); +} + +#[test] +fn test_setglobal() { + eval(" + def test + $a = 1 + $a + end + test + "); + assert_contains_opcode("test", YARVINSN_setglobal); + assert_snapshot!(assert_compiles("test"), @"1"); +} + +#[test] +fn test_string_intern() { + eval(r#" + def test + :"foo#{123}" + end + test + "#); + assert_contains_opcode("test", YARVINSN_intern); + assert_snapshot!(assert_compiles(r#"test"#), @":foo123"); +} + +#[test] +fn test_duphash() { + eval(" + def test + {a: 1} + end + test + "); + assert_contains_opcode("test", YARVINSN_duphash); + assert_snapshot!(assert_compiles("test"), @"{a: 1}"); +} + +#[test] +fn test_pushtoarray() { + eval(" + def test + [*[], 1, 2, 3] + end + test + "); + assert_contains_opcode("test", YARVINSN_pushtoarray); + assert_snapshot!(assert_compiles("test"), @"[1, 2, 3]"); +} + +#[test] +fn test_splatarray_new_array() { + eval(" + def test a + [*a, 3] + end + test [1, 2] + "); + assert_contains_opcode("test", YARVINSN_splatarray); + assert_snapshot!(assert_compiles("test [1, 2]"), @"[1, 2, 3]"); +} + +#[test] +fn test_splatarray_existing_array() { + eval(" + def foo v + [1, 2, v] + end + def test a + foo(*a) + end + test [3] + "); + assert_contains_opcode("test", YARVINSN_splatarray); + assert_snapshot!(assert_compiles("test [3]"), @"[1, 2, 3]"); +} + +#[test] +fn test_concattoarray() { + eval(" + def test(*a) + [1, 2, *a] + end + test 3 + "); + assert_contains_opcode("test", YARVINSN_concattoarray); + assert_snapshot!(assert_compiles("test 3"), @"[1, 2, 3]"); +} + +#[test] +fn test_definedivar() { + eval(" + def test + v0 = defined?(@a) + @a = nil + v1 = defined?(@a) + remove_instance_variable :@a + v2 = defined?(@a) + [v0, v1, v2] + end + test + "); + assert_contains_opcode("test", YARVINSN_definedivar); + assert_snapshot!(assert_compiles("test"), @r#"[nil, "instance-variable", nil]"#); +} + +#[test] +fn test_setglobal_with_trace_var_exception() { + eval(r#" + def test + $a = 1 + rescue + "rescued" + end + trace_var(:$a) { raise } + test + "#); + assert_contains_opcode("test", YARVINSN_setglobal); + assert_snapshot!(assert_compiles(r#"test"#), @r#""rescued""#); +} + +#[test] +fn test_getlocal_after_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_getlocal_after_instance_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + instance_eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_getlocal_after_module_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + Kernel.module_eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_getlocal_after_class_eval() { + assert_snapshot!(inspect(" + def test + a = 1 + Kernel.class_eval('a = 2') + a + end + test + test + "), @"2"); +} + +#[test] +fn test_setlocal() { + assert_snapshot!(inspect(" + def test(n) + m = n + m + end + test(3) + test(3) + "), @"3"); +} + +#[test] +fn test_return_nonparam_local() { + assert_snapshot!(inspect(" + def foo(a) + if false + x = nil + end + x + end + def test = foo(1) + test + test + "), @"nil"); +} + +#[test] +fn test_nonparam_local_nil_in_jit_call() { + assert_snapshot!(inspect(r#" + def f(a) + a ||= 1 + if false; b = 1; end + eval("-> { p 'x#{b}' }") + end + + 4.times.map { f(1).call } + "#), @r#"["x", "x", "x", "x"]"#); +} + +#[test] +fn test_kwargs_with_exit_and_local_invalidation() { + assert_snapshot!(inspect(r#" + def a(b:, c:) + if c == :b + return -> {} + end + Class # invalidate locals + + raise "c is :b!" if c == :b + end + + def test + # note opposite order of kwargs + a(c: :c, b: :b) + end + + 4.times { test } + :ok + "#), @":ok"); +} + +#[test] +fn test_kwargs_with_max_direct_send_arg_count() { + assert_snapshot!(inspect(" + def kwargs(five, six, a:, b:, c:, d:, e:, f:) + [a, b, c, d, five, six, e, f] + end + + 5.times.flat_map do + [ + kwargs(5, 6, d: 4, c: 3, a: 1, b: 2, e: 7, f: 8), + kwargs(5, 6, d: 4, c: 3, b: 2, a: 1, e: 7, f: 8) + ] + end.uniq + "), @"[[1, 2, 3, 4, 5, 6, 7, 8]]"); +} + +#[test] +fn test_setlocal_on_eval() { + assert_snapshot!(inspect(" + @b = binding + eval('a = 1', @b) + eval('a', @b) + "), @"1"); +} + +#[test] +fn test_optional_arguments() { + assert_snapshot!(inspect(" + def test(a, b = 2, c = 3) + [a, b, c] + end + [test(1), test(10, 20), test(100, 200, 300)] + "), @"[[1, 2, 3], [10, 20, 3], [100, 200, 300]]"); +} + +#[test] +fn test_optional_arguments_setlocal() { + assert_snapshot!(inspect(" + def test(a = (b = 2)) + [a, b] + end + [test, test(1)] + "), @"[[2, 2], [1, nil]]"); +} + +#[test] +fn test_optional_arguments_cyclic() { + assert_snapshot!(inspect(" + test = proc { |a=a| a } + [test.call, test.call(1)] + "), @"[nil, 1]"); +} + +#[test] +fn test_getblockparamproxy() { + eval(" + def test(&block) + 0.then(&block) + end + test { 1 } + "); + assert_contains_opcode("test", YARVINSN_getblockparamproxy); + assert_snapshot!(assert_compiles("test { 1 }"), @"1"); +} + +#[test] +fn test_getblockparamproxy_modified() { + eval(" + def test(&block) + b = block + 0.then(&block) + end + test { 1 } + "); + assert_contains_opcode("test", YARVINSN_getblockparamproxy); + assert_snapshot!(inspect("test { 1 }"), @"1"); +} + +#[test] +fn test_getblockparamproxy_modified_nested_block() { + eval(" + def test(&block) + proc do + b = block + 0.then(&block) + end + end + test { 1 }.call + "); + assert_snapshot!(inspect("test { 1 }.call"), @"1"); +} + +#[test] +fn test_getblockparamproxy_polymorphic_none_and_iseq() { + set_call_threshold(3); + eval(" + def test(&block) + 0.then(&block) + end + test + test { 1 } + "); + assert_contains_opcode("test", YARVINSN_getblockparamproxy); + assert_snapshot!(assert_compiles("test { 2 }"), @"2"); +} + +#[test] +fn test_getblockparam() { + eval(" + def test(&blk) + blk + end + test { 2 }.call + "); + assert_contains_opcode("test", YARVINSN_getblockparam); + assert_snapshot!(assert_compiles("test { 2 }.call"), @"2"); +} + +#[test] +fn test_setblockparam() { + eval(" + def test(&block) + block = proc { 3 } + blk = block + blk.call + end + test { 1 } + "); + assert_contains_opcode("test", YARVINSN_setblockparam); + assert_snapshot!(assert_compiles("test { 1 }"), @"3"); +} + +#[test] +fn test_setblockparam_nested_block() { + eval(" + def test(&block) + proc do + block = proc { 3 } + blk = block + blk.call + end.call + end + test { 1 } + "); + assert_snapshot!(assert_compiles("test { 1 }"), @"3"); +} + +#[test] +fn test_getblockparamproxy_after_setblockparam() { + eval(" + def test(&block) + block = proc { 3 } + block.call + end + test { 1 } + "); + assert_contains_opcode("test", YARVINSN_setblockparam); + assert_snapshot!(assert_compiles("test { 1 }"), @"3"); +} + +#[test] +fn test_getblockparam_used_twice_in_args() { + eval(" + def f(*args) = args + def test(&blk) + b = blk + f(*[1], blk) + blk + end + test {1}.call + "); + assert_contains_opcode("test", YARVINSN_getblockparam); + assert_snapshot!(assert_compiles("test {1}.call"), @"1"); +} + +#[test] +fn test_optimized_method_call_proc_call() { + eval(" + def test(p) + p.call(1) + end + test(proc { |x| x * 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("test(proc { |x| x * 2 })"), @"2"); +} + +#[test] +fn test_optimized_method_call_proc_aref() { + eval(" + def test(p) + p[2] + end + test(proc { |x| x * 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles("test(proc { |x| x * 2 })"), @"4"); +} + +#[test] +fn test_optimized_method_call_proc_yield() { + eval(" + def test(p) + p.yield(3) + end + test(proc { |x| x * 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("test(proc { |x| x * 2 })"), @"6"); +} + +#[test] +fn test_optimized_method_call_proc_kw_splat() { + eval(" + def test(p, h) + p.call(**h) + end + test(proc { |**kw| kw[:a] + kw[:b] }, { a: 1, b: 2 }) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("test(proc { |**kw| kw[:a] + kw[:b] }, { a: 1, b: 2 })"), @"3"); +} + +#[test] +fn test_optimized_method_call_proc_call_splat() { + assert_snapshot!(inspect(" + p = proc { |x| x + 1 } + def test(p) + ary = [42] + p.call(*ary) + end + test(p) + test(p) + "), @"43"); +} + +#[test] +fn test_optimized_method_call_proc_call_kwarg() { + assert_snapshot!(inspect(" + p = proc { |a:| a } + def test(p) + p.call(a: 1) + end + test(p) + test(p) + "), @"1"); +} + +#[test] +fn test_setlocal_on_eval_with_spill() { + assert_snapshot!(inspect(" + @b = binding + eval('a = 1; itself', @b) + eval('a', @b) + "), @"1"); +} + +#[test] +fn test_nested_local_access() { + assert_snapshot!(inspect(" + 1.times do |l2| + 1.times do |l1| + define_method(:test) do + l1 = 1 + l2 = 2 + l3 = 3 + [l1, l2, l3] + end + end + end + + test + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_send_with_local_written_by_blockiseq() { + assert_snapshot!(inspect(" + def test + l1 = nil + l2 = nil + tap do |_| + l1 = 1 + tap do |_| + l2 = 2 + end + end + + [l1, l2] + end + + test + test + "), @"[1, 2]"); +} + +#[test] +fn test_no_ep_escape_patch_point_after_send_does_not_repeat_send() { + eval(r#" + $send_count = 0 + + def test + captured = nil + tap do |_| + $send_count += 1 + -> { captured } if $send_count == 2 + end + $send_count + end + "#); + assert_contains_opcode("test", YARVINSN_send); + assert_snapshot!(assert_compiles_allowing_exits("[test, test, test]"), @"[1, 2, 3]"); +} + +#[test] +fn test_send_without_block() { + assert_snapshot!(inspect(" + def foo = 1 + def bar(a) = a - 1 + def baz(a, b) = a - b + + def test1 = foo + def test2 = bar(3) + def test3 = baz(4, 1) + + [test1, test2, test3] + "), @"[1, 2, 3]"); +} + +#[test] +fn test_send_with_six_args() { + assert_snapshot!(inspect(" + def foo(a1, a2, a3, a4, a5, a6) + [a1, a2, a3, a4, a5, a6] + end + + def test + foo(1, 2, 3, 4, 5, 6) + end + + test # profile send + test + "), @"[1, 2, 3, 4, 5, 6]"); +} + +#[test] +fn test_send_optional_arguments() { + assert_snapshot!(inspect(" + def test(a, b = 2) = [a, b] + def entry = [test(1), test(3, 4)] + entry + entry + "), @"[[1, 2], [3, 4]]"); +} + +#[test] +fn test_send_nil_block_arg() { + assert_snapshot!(inspect(" + def test = block_given? + def entry = test(&nil) + test + test + "), @"false"); +} + +#[test] +fn test_send_symbol_block_arg() { + assert_snapshot!(inspect(" + def test = [1, 2].map(&:to_s) + test + test + "), @r#"["1", "2"]"#); +} + +#[test] +fn test_send_variadic_with_block() { + assert_snapshot!(inspect(" + A = [1, 2, 3] + B = [\"a\", \"b\", \"c\"] + + def test + result = [] + A.zip(B) { |x, y| result << [x, y] } + result + end + + test; test + "), @r#"[[1, "a"], [2, "b"], [3, "c"]]"#); +} + +#[test] +fn test_send_kwarg_optional() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2) = [a, b] + def entry = test + entry + entry + "), @"[1, 2]"); +} + +#[test] +fn test_send_kwarg_optional_too_many() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10) = [a, b, c, d, e, f, g, h, i, j] + def entry = test + entry + entry + "), @"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"); +} + +#[test] +fn test_send_kwarg_required_and_optional() { + assert_snapshot!(inspect(" + def test(a:, b: 2) = [a, b] + def entry = test(a: 3) + entry + entry + "), @"[3, 2]"); +} + +#[test] +fn test_send_kwarg_to_hash() { + assert_snapshot!(inspect(" + def test(hash) = hash + def entry = test(a: 3) + entry + entry + "), @"{a: 3}"); +} + +#[test] +fn test_send_kwarg_to_ccall() { + assert_snapshot!(inspect(r#" + def test(s) = s.each_line(chomp: true).to_a + def entry = test(%(a\nb\nc)) + entry + entry + "#), @r#"["a", "b", "c"]"#); +} + +#[test] +fn test_send_kwarg_and_block_to_ccall() { + assert_snapshot!(inspect(r#" + def test(s) + a = [] + s.each_line(chomp: true) { |l| a << l } + a + end + def entry = test(%(a\nb\nc)) + entry + entry + "#), @r#"["a", "b", "c"]"#); +} + +#[test] +fn test_send_kwarg_with_too_many_args_to_c_call() { + assert_snapshot!(inspect(r#" + def test(a:, b:, c:, d:, e:) = sprintf("%s %s %s %s %s", a, b, c, d, kwargs: e) + def entry = test(e: :e, d: :d, c: :c, a: :a, b: :b) + entry + entry + "#), @r#""a b c d {kwargs: :e}""#); +} + +#[test] +fn test_send_kwsplat() { + assert_snapshot!(inspect(" + def test(a:) = a + def entry = test(**{a: 3}) + entry + entry + "), @"3"); +} + +#[test] +fn test_send_kwrest() { + assert_snapshot!(inspect(" + def test(**kwargs) = kwargs + def entry = test(a: 3) + entry + entry + "), @"{a: 3}"); +} + +#[test] +fn test_send_req_kwreq() { + assert_snapshot!(inspect(" + def test(a, c:) = [a, c] + def entry = test(1, c: 3) + entry + entry + "), @"[1, 3]"); +} + +#[test] +fn test_send_req_opt_kwreq() { + assert_snapshot!(inspect(" + def test(a, b = 2, c:) = [a, b, c] + def entry = [test(1, c: 3), test(-1, -2, c: -3)] + entry + entry + "), @"[[1, 2, 3], [-1, -2, -3]]"); +} + +#[test] +fn test_send_req_opt_kwreq_kwopt() { + assert_snapshot!(inspect(" + def test(a, b = 2, c:, d: 4) = [a, b, c, d] + def entry = [test(1, c: 3), test(-1, -2, d: -4, c: -3)] + entry + entry + "), @"[[1, 2, 3, 4], [-1, -2, -3, -4]]"); +} + +#[test] +fn test_send_unexpected_keyword() { + assert_snapshot!(inspect(" + def test(a: 1) = a*2 + def entry + test(z: 2) + rescue ArgumentError + :error + end + + entry + entry + "), @":error"); +} + +#[test] +fn test_pos_optional_with_maybe_too_many_args() { + assert_snapshot!(inspect(" + def target(a = 1, b = 2, c = 3, d = 4, e = 5, f:) = [a, b, c, d, e, f] + def test = [target(f: 6), target(10, 20, 30, f: 6), target(10, 20, 30, 40, 50, f: 60)] + test + test + "), @"[[1, 2, 3, 4, 5, 6], [10, 20, 30, 4, 5, 6], [10, 20, 30, 40, 50, 60]]"); +} + +#[test] +fn test_send_kwarg_partial_optional() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2, c: 3) = [a, b, c] + def entry = [test, test(b: 20), test(c: 30, a: 10)] + entry + entry + "), @"[[1, 2, 3], [1, 20, 3], [10, 2, 30]]"); +} + +#[test] +fn test_send_kwarg_optional_a_lot() { + assert_snapshot!(inspect(" + def test(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6) = [a, b, c, d, e, f] + def entry = [test, test(d: 7, f: 9, e: 8), test(f: 12, e: 10, d: 8, c: 6, b: 4, a: 2)] + entry + entry + "), @"[[1, 2, 3, 4, 5, 6], [1, 2, 3, 7, 8, 9], [2, 4, 6, 8, 10, 12]]"); +} + +#[test] +fn test_send_kwarg_non_constant_default() { + assert_snapshot!(inspect(" + def make_default = 2 + def test(a: 1, b: make_default) = [a, b] + def entry = [test, test(a: 10)] + entry + entry + "), @"[[1, 2], [10, 2]]"); +} + +#[test] +fn test_send_kwarg_optional_static_with_side_exit() { + assert_snapshot!(inspect(" + def callee(a: 1, b: 2) + x = binding.local_variable_get(:a) + [a, b, x] + end + + def entry + callee(a: 10) + end + + entry + entry + "), @"[10, 2, 10]"); +} + +#[test] +fn test_send_hash_to_kwarg_only_method() { + assert_snapshot!(inspect(r#" + def callee(a:) = a + + def entry + callee({a: 1}) + rescue ArgumentError + "ArgumentError" + end + + entry + entry + "#), @r#""ArgumentError""#); +} + +#[test] +fn test_send_hash_to_optional_kwarg_only_method() { + assert_snapshot!(inspect(r#" + def callee(a: nil) = a + + def entry + callee({a: 1}) + rescue ArgumentError + "ArgumentError" + end + + entry + entry + "#), @r#""ArgumentError""#); +} + +#[test] +fn test_send_all_arg_types() { + assert_snapshot!(inspect(" + def test(a, b = :opt, c, d:, e: :kwo) = [a, b, c, d, e, block_given?] + def entry = test(:req, :post, d: :kwr) {} + entry + entry + "), @"[:req, :opt, :post, :kwr, :kwo, true]"); +} + +#[test] +fn test_send_ccall_variadic_with_different_receiver_classes() { + assert_snapshot!(inspect(r#" + def test(obj) = obj.start_with?("a") + [test("abc"), test(:abc)] + "#), @"[true, true]"); +} + +#[test] +fn test_forwardable_iseq() { + assert_snapshot!(inspect(" + def test(...) = 1 + test + test + "), @"1"); +} + +#[test] +fn test_sendforward() { + eval(" + def callee(a, b) = [a, b] + def test(...) = callee(...) + test(1, 2) + "); + assert_contains_opcode("test", YARVINSN_sendforward); + assert_snapshot!(assert_compiles("test(1, 2)"), @"[1, 2]"); +} + +#[test] +fn test_iseq_with_optional_arguments() { + assert_snapshot!(inspect(" + def test(a, b = 2) = [a, b] + [test(1), test(3, 4)] + "), @"[[1, 2], [3, 4]]"); +} + +#[test] +fn test_invokesuper() { + assert_snapshot!(inspect(" + class Foo + def foo(a) = a + 1 + def bar(a) = a + 10 + end + + class Bar < Foo + def foo(a) = super(a) + 2 + def bar(a) = super + 20 + end + + bar = Bar.new + [bar.foo(3), bar.bar(30)] + "), @"[6, 60]"); +} + +#[test] +fn test_invokesuper_to_iseq() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "A"]"#); +} + +#[test] +fn test_invokesuper_with_args() { + assert_snapshot!(inspect(r#" + class A + def foo(x) + x * 2 + end + end + + class B < A + def foo(x) + ["B", super(x) + 1] + end + end + + def test + B.new.foo(5) + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", 11]"#); +} + +#[test] +fn test_invokesuper_with_args_to_rest_param() { + assert_snapshot!(inspect(r#" + class A + def foo(x, *rest) + [x, rest] + end + end + + class B < A + def foo(x, y, z) + ["B", *super(x, y, z)] + end + end + + def test + B.new.foo("a", "b", "c") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "a", ["b", "c"]]"#); +} + +#[test] +fn test_invokesuper_with_block() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super { "from_block" }] + end + end + + def test + B.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "from_block"]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_no_args() { + assert_snapshot!(inspect(r#" + class MyString < String + def length + ["MyString", super] + end + end + + def test + MyString.new("abc").length + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", 3]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_simple_args() { + assert_snapshot!(inspect(r#" + class MyString < String + def include?(other) + ["MyString", super(other)] + end + end + + def test + MyString.new("abc").include?("bc") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", true]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_with_optional_arg() { + assert_snapshot!(inspect(r#" + class MyString < String + def byteindex(needle, offset = 0) + ["MyString", super(needle, offset)] + end + end + + def test + MyString.new("hello world").byteindex("world") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", 6]"#); +} + +#[test] +fn test_invokesuper_to_cfunc_varargs() { + assert_snapshot!(inspect(r#" + class MyString < String + def end_with?(str) + ["MyString", super(str)] + end + end + + def test + MyString.new("abc").end_with?("bc") + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["MyString", true]"#); +} + +#[test] +fn test_string_new_preserves_string_arg() { + assert_snapshot!(inspect(r#" + def test + str = "hello" + String.new(str) + :ok + end + + test + test + "#), @":ok"); +} + +#[test] +fn test_invokesuper_multilevel() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + class C < B + def foo + ["C", super] + end + end + + def test + C.new.foo + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["C", ["B", "A"]]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # should forward the block from caller + end + end + + def test + B.new.foo { "forwarded_block" } + end + + test # profile invokesuper + test # compile + run compiled code + "#), @r#"["B", "forwarded_block"]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly_with_args() { + assert_snapshot!(inspect(r#" + class A + def foo(x) + [x, (block_given? ? yield : "no_block")] + end + end + + class B < A + def foo(x) + ["B", super(x)] # explicit args, but block should still be forwarded + end + end + + def test + B.new.foo("arg_value") { "forwarded" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", ["arg_value", "forwarded"]]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly_no_block_given() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # no block given by caller + end + end + + def test + B.new.foo # called without a block + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "no_block"]"#); +} + +#[test] +fn test_invokesuper_forwards_block_implicitly_multilevel() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo + ["B", super] # forwards block to A + end + end + + class C < B + def foo + ["C", super] # forwards block to B, which forwards to A + end + end + + def test + C.new.foo { "deep_block" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["C", ["B", "deep_block"]]"#); +} + +#[test] +fn test_invokesuper_forwards_block_param() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no_block" + end + end + + class B < A + def foo(&block) + ["B", super] # should forward &block implicitly + end + end + + def test + B.new.foo { "block_param_forwarded" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "block_param_forwarded"]"#); +} + +#[test] +fn test_invokesuper_with_blockarg() { + assert_snapshot!(inspect(r#" + class A + def foo + block_given? ? yield : "no block" + end + end + + class B < A + def foo(&blk) + other_block = proc { "different block" } + ["B", super(&other_block)] + end + end + + def test + B.new.foo { "passed block" } + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "different block"]"#); +} + +#[test] +fn test_invokesuper_with_symbol_to_proc() { + assert_snapshot!(inspect(r#" + class A + def foo(items, &blk) + items.map(&blk) + end + end + + class B < A + def foo(items) + ["B", super(items, &:succ)] + end + end + + def test + B.new.foo([2, 4, 6]) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", [3, 5, 7]]"#); +} + +#[test] +fn test_invokesuper_with_splat() { + assert_snapshot!(inspect(r#" + class A + def foo(a, b, c) + a + b + c + end + end + + class B < A + def foo(*args) + ["B", super(*args)] + end + end + + def test + B.new.foo(1, 2, 3) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", 6]"#); +} + +#[test] +fn test_invokesuper_with_kwargs() { + assert_snapshot!(inspect(r#" + class A + def foo(x:, y:) + "x=#{x}, y=#{y}" + end + end + + class B < A + def foo(x:, y:) + ["B", super(x: x, y: y)] + end + end + + def test + B.new.foo(x: 1, y: 2) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "x=1, y=2"]"#); +} + +#[test] +fn test_invokesuper_with_kw_splat() { + assert_snapshot!(inspect(r#" + class A + def foo(x:, y:) + "x=#{x}, y=#{y}" + end + end + + class B < A + def foo(**kwargs) + ["B", super(**kwargs)] + end + end + + def test + B.new.foo(x: 1, y: 2) + end + + test # profile + test # compile + run compiled code + "#), @r#"["B", "x=1, y=2"]"#); +} + +#[test] +fn test_invokesuper_with_include() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper (super -> A#foo) + test # compile with super -> A#foo + + # Now include a module in B that defines foo - super should go to M#foo instead + module M + def foo + "M" + end + end + B.include(M) + + test # should call M#foo, not A#foo + "#), @r#"["B", "M"]"#); +} + +#[test] +fn test_invokesuper_with_prepend() { + assert_snapshot!(inspect(r#" + class A + def foo + "A" + end + end + + class B < A + def foo + ["B", super] + end + end + + def test + B.new.foo + end + + test # profile invokesuper (super -> A#foo) + test # compile with super -> A#foo + + # Now prepend a module that defines foo - super should go to M#foo instead + module M + def foo + "M" + end + end + A.prepend(M) + + test # should call M#foo, not A#foo + "#), @r#"["B", "M"]"#); +} + +#[test] +fn test_invokesuper_with_keyword_args() { + assert_snapshot!(inspect(r#" + class A + def foo(attributes = {}) + @attributes = attributes + end + end + + class B < A + def foo(content = '') + super(content: content) + end + end + + def test + B.new.foo("image data") + end + + test + test + "#), @r#"{content: "image data"}"#); +} + +#[test] +fn test_invokesuper_with_optional_keyword_args() { + assert_snapshot!(inspect(" + class Parent + def foo(a, b: 2, c: 3) = [a, b, c] + end + + class Child < Parent + def foo(a) = super(a) + end + + def test = Child.new.foo(1) + + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_invokesuperforward() { + assert_snapshot!(inspect(" + class A + def foo(a,b,c) = [a,b,c] + end + + class B < A + def foo(...) = super + end + + def test + B.new.foo(1, 2, 3) + end + + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_invokesuperforward_with_args_kwargs_and_block() { + assert_snapshot!(inspect(" + class A + def foo(*args, **kwargs, &block) + [args, kwargs, block&.call] + end + end + + class B < A + def foo(...) = super + end + + def test + B.new.foo(1, 2, x: 3) { 4 } + end + + test + test + "), @"[[1, 2], {x: 3}, 4]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default() { + assert_snapshot!(inspect(" + def dbl(x = 1) = x * 2 + + def foo(a: dbl, b: dbl(2), c: dbl(2 ** 3)) + [a, b, c] + end + + def test + [ + foo, + foo(a: 10), + foo(b: 20), + foo(c: 30), + foo(a: 10, b: 20, c: 30) + ] + end + + test + test + "), @"[[2, 4, 16], [10, 4, 16], [2, 20, 16], [2, 4, 30], [10, 20, 30]]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_not_evaluated_when_provided() { + assert_snapshot!(inspect(" + def foo(a: raise, b: raise, c: raise) + [a, b, c] + end + + def test + foo(a: 1, b: 2, c: 3) + end + + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_evaluated_when_not_provided() { + assert_snapshot!(inspect(r#" + def raise_a = raise "a" + def raise_b = raise "b" + def raise_c = raise "c" + + def foo(a: raise_a, b: raise_b, c: raise_c) + [a, b, c] + end + + def test_a + foo(b: 2, c: 3) + rescue RuntimeError => e + e.message + end + + def test_b + foo(a: 1, c: 3) + rescue RuntimeError => e + e.message + end + + def test_c + foo(a: 1, b: 2) + rescue RuntimeError => e + e.message + end + + def test + [test_a, test_b, test_c] + end + + test + test + "#), @r#"["a", "b", "c"]"#); +} + +#[test] +fn test_send_with_non_constant_keyword_default_jit_to_jit() { + assert_snapshot!(inspect(" + def make_default(x) = x * 2 + + def callee(a: make_default(1), b: make_default(2), c: make_default(3)) + [a, b, c] + end + + def caller_method + callee + end + + # Warm up callee first so it gets JITted + callee + callee + + # Now warm up caller - this creates JIT-to-JIT call + caller_method + caller_method + "), @"[2, 4, 6]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_side_exit() { + assert_snapshot!(inspect(" + def make_b = 2 + + def callee(a: 1, b: make_b, c: 3) + x = binding.local_variable_get(:a) + y = binding.local_variable_get(:b) + z = binding.local_variable_get(:c) + [x, y, z] + end + + def test + callee(a: 10, c: 30) + end + + test + test + "), @"[10, 2, 30]"); +} + +#[test] +fn test_send_with_non_constant_keyword_default_evaluation_order() { + assert_snapshot!(inspect(r#" + def log(x) + $order << x + x + end + + def foo(a: log("a"), b: log("b"), c: log("c")) + [a, b, c] + end + + def test + results = [] + + $order = [] + foo + results << $order.dup + + $order = [] + foo(a: "A") + results << $order.dup + + $order = [] + foo(b: "B") + results << $order.dup + + $order = [] + foo(c: "C") + results << $order.dup + + results + end + + test + test + "#), @r#"[["a", "b", "c"], ["b", "c"], ["a", "c"], ["a", "b"]]"#); +} + +#[test] +fn test_send_with_too_many_non_constant_keyword_defaults() { + assert_snapshot!(inspect(" + def many_kwargs( k1: 1, k2: 2, k3: 3, k4: 4, k5: 5, k6: 6, k7: 7, k8: 8, k9: 9, k10: 10, k11: 11, k12: 12, k13: 13, k14: 14, k15: 15, k16: 16, k17: 17, k18: 18, k19: 19, k20: 20, k21: 21, k22: 22, k23: 23, k24: 24, k25: 25, k26: 26, k27: 27, k28: 28, k29: 29, k30: 30, k31: 31, k32: 32, k33: 33, k34: k33 + 1) = k1 + k34 + def t = many_kwargs + t + t + "), @"35"); +} + +#[test] +fn test_invokebuiltin_delegate() { + assert_snapshot!(inspect(" + def test = [].clone(freeze: true) + r = test + r2 = test + [r2, r2.frozen?] + "), @"[[], true]"); +} + +#[test] +fn test_opt_plus_const() { + assert_snapshot!(inspect(" + def test = 1 + 2 + test # profile opt_plus + test + "), @"3"); +} + +#[test] +fn test_opt_plus_fixnum() { + assert_snapshot!(inspect(" + def test(a, b) = a + b + test(0, 1) # profile opt_plus + test(1, 2) + "), @"3"); +} + +#[test] +fn test_opt_plus_chain() { + assert_snapshot!(inspect(" + def test(a, b, c) = a + b + c + test(0, 1, 2) # profile opt_plus + test(1, 2, 3) + "), @"6"); +} + +#[test] +fn test_opt_plus_left_imm() { + assert_snapshot!(inspect(" + def test(a) = 1 + a + test(1) # profile opt_plus + test(2) + "), @"3"); +} + +#[test] +fn test_opt_plus_type_guard_exit() { + assert_snapshot!(inspect(" + def test(a) = 1 + a + test(1) # profile opt_plus + [test(2), test(2.0)] + "), @"[3, 3.0]"); +} + +#[test] +fn test_opt_plus_type_guard_exit_with_locals() { + assert_snapshot!(inspect(" + def test(a) + local = 3 + 1 + a + local + end + test(1) # profile opt_plus + [test(2), test(2.0)] + "), @"[6, 6.0]"); +} + +#[test] +fn test_opt_plus_type_guard_nested_exit() { + assert_snapshot!(inspect(" + def side_exit(n) = 1 + n + def jit_frame(n) = 1 + side_exit(n) + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + "), @"[4, 4.0]"); +} + +#[test] +fn test_opt_plus_type_guard_nested_exit_with_locals() { + assert_snapshot!(inspect(" + def side_exit(n) + local = 2 + 1 + n + local + end + def jit_frame(n) + local = 3 + 1 + side_exit(n) + local + end + def entry(n) = jit_frame(n) + entry(2) # profile send + [entry(2), entry(2.0)] + "), @"[9, 9.0]"); +} + +#[test] +fn test_opt_minus() { + assert_snapshot!(inspect(" + def test(a, b) = a - b + test(2, 1) # profile opt_minus + test(6, 4) + "), @"2"); +} + +#[test] +fn test_opt_mult() { + assert_snapshot!(inspect(" + def test(a, b) = a * b + test(1, 2) # profile opt_mult + test(2, 3) + "), @"6"); +} + +#[test] +fn test_opt_mult_overflow() { + assert_snapshot!(inspect(" + def test(a, b) + a * b + end + test(1, 1) # profile opt_mult + + r1 = test(2, 3) + r2 = test(2, -3) + r3 = test(2 << 40, 2 << 41) + r4 = test(2 << 40, -2 << 41) + r5 = test(1 << 62, 1 << 62) + + [r1, r2, r3, r4, r5] + "), @"[6, -6, 9671406556917033397649408, -9671406556917033397649408, 21267647932558653966460912964485513216]"); +} + +#[test] +fn test_opt_eq() { + eval(" + def test(a, b) = a == b + test(0, 2) # profile opt_eq + "); + assert_contains_opcode("test", YARVINSN_opt_eq); + assert_snapshot!(assert_compiles("[test(1, 1), test(0, 1)]"), @"[true, false]"); +} + +#[test] +fn test_opt_eq_with_minus_one() { + eval(" + def test(a) = a == -1 + test(1) # profile opt_eq + "); + assert_contains_opcode("test", YARVINSN_opt_eq); + assert_snapshot!(assert_compiles("[test(0), test(-1)]"), @"[false, true]"); +} + +#[test] +fn test_opt_neq_dynamic() { + eval(" + def test(a, b) = a != b + test(0, 2) # profile opt_neq + "); + assert_contains_opcode("test", YARVINSN_opt_neq); + assert_snapshot!(assert_compiles("[test(1, 1), test(0, 1)]"), @"[false, true]"); +} + +#[test] +fn test_opt_neq_fixnum() { + assert_snapshot!(inspect(" + def test(a, b) = a != b + test(0, 2) # profile opt_neq + [test(1, 1), test(0, 1)] + "), @"[false, true]"); +} + +#[test] +fn test_opt_neq_string_nil() { + assert_snapshot!(inspect(r#" + def test(str) = str != nil + test("x") # profile opt_neq + [test("x"), test(nil)] + "#), @"[true, false]"); +} + +#[test] +fn test_opt_neq_string_same_operand() { + assert_snapshot!(inspect(r#" + def test(s) = s != s + test("x") # profile opt_neq + [test("x"), test("y")] + "#), @"[false, false]"); + assert_contains_opcode("test", YARVINSN_opt_neq); +} + +#[test] +fn test_opt_neq_string_distinct_literals() { + assert_snapshot!(inspect(r#" + def test = "a" != "b" + test # profile opt_neq + [test, test] + "#), @"[true, true]"); + assert_contains_opcode("test", YARVINSN_opt_neq); +} + +#[test] +fn test_opt_neq_string_one_side_known_literal() { + assert_snapshot!(inspect(r#" + def test(s) = "a" != s + test("a") # profile opt_neq + [test("a"), test("b")] + "#), @"[false, true]"); + assert_contains_opcode("test", YARVINSN_opt_neq); +} + +#[test] +fn test_opt_neq_string_distinct_objects() { + assert_snapshot!(inspect(r#" + def test(s, t) = s != t + test("x", "x") # profile opt_neq + [test("x", "x"), test("x", "y")] + "#), @"[false, true]"); + assert_contains_opcode("test", YARVINSN_opt_neq); +} + +#[test] +fn test_opt_eq_string_same_operand() { + assert_snapshot!(inspect(r#" + def test(s) = s == s + test("x") # profile opt_eq + [test("x"), test("y")] + "#), @"[true, true]"); + assert_contains_opcode("test", YARVINSN_opt_eq); +} + +#[test] +fn test_opt_eq_string_distinct_literals() { + assert_snapshot!(inspect(r#" + def test = "a" == "b" + test # profile opt_eq + [test, test] + "#), @"[false, false]"); + assert_contains_opcode("test", YARVINSN_opt_eq); +} + +#[test] +fn test_opt_eq_string_one_side_known_literal() { + assert_snapshot!(inspect(r#" + def test(s) = "a" == s + test("a") # profile opt_eq + [test("a"), test("b")] + "#), @"[true, false]"); + assert_contains_opcode("test", YARVINSN_opt_eq); +} + +#[test] +fn test_opt_eq_string_distinct_objects() { + assert_snapshot!(inspect(r#" + def test(s, t) = s == t + test("x", "x") # profile opt_eq + [test("x", "x"), test("x", "y")] + "#), @"[true, false]"); + assert_contains_opcode("test", YARVINSN_opt_eq); +} + +#[test] +fn test_opt_eqq_string_same_operand() { + assert_snapshot!(inspect(r#" + def test(s) = s === s + test("x") # profile opt_send_without_block + [test("x"), test("y")] + "#), @"[true, true]"); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); +} + +#[test] +fn test_opt_lt() { + eval(" + def test(a, b) = a < b + test(2, 3) # profile opt_lt + "); + assert_contains_opcode("test", YARVINSN_opt_lt); + assert_snapshot!(assert_compiles("[test(0, 1), test(0, 0), test(1, 0)]"), @"[true, false, false]"); +} + +#[test] +fn test_opt_lt_with_literal_lhs() { + eval(" + def test(n) = 2 < n + test(2) # profile opt_lt + "); + assert_contains_opcode("test", YARVINSN_opt_lt); + assert_snapshot!(assert_compiles("[test(1), test(2), test(3)]"), @"[false, false, true]"); +} + +#[test] +fn test_opt_le() { + eval(" + def test(a, b) = a <= b + test(2, 3) # profile opt_le + "); + assert_contains_opcode("test", YARVINSN_opt_le); + assert_snapshot!(assert_compiles("[test(0, 1), test(0, 0), test(1, 0)]"), @"[true, true, false]"); +} + +#[test] +fn test_opt_gt() { + eval(" + def test(a, b) = a > b + test(2, 3) # profile opt_gt + "); + assert_contains_opcode("test", YARVINSN_opt_gt); + assert_snapshot!(assert_compiles("[test(0, 1), test(0, 0), test(1, 0)]"), @"[false, false, true]"); +} + +#[test] +fn test_opt_empty_p() { + eval(" + def test(x) = x.empty? + "); + assert_contains_opcode("test", YARVINSN_opt_empty_p); + assert_snapshot!(assert_compiles_allowing_exits("[test([1]), test(\"1\"), test({})]"), @"[false, false, true]"); +} + +#[test] +fn test_opt_succ() { + eval(" + def test(obj) = obj.succ + "); + assert_contains_opcode("test", YARVINSN_opt_succ); + assert_snapshot!(assert_compiles_allowing_exits(r#"[test(-1), test("A")]"#), @r#"[0, "B"]"#); +} + +#[test] +fn test_opt_and() { + eval(" + def test(x, y) = x & y + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(assert_compiles_allowing_exits("[test(0b1101, 3), test([3, 2, 1, 4], [8, 1, 2, 3])]"), @"[1, [3, 2, 1]]"); +} + +#[test] +fn test_opt_or() { + eval(" + def test(x, y) = x | y + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(assert_compiles_allowing_exits("[test(0b1000, 3), test([3, 2, 1], [1, 2, 3])]"), @"[11, [3, 2, 1]]"); +} + +#[test] +fn test_fixnum_and() { + eval(" + def test(a, b) = a & b + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(assert_compiles(" + [ + test(5, 3), + test(0b011, 0b110), + test(-0b011, 0b110) + ] + "), @"[1, 2, 4]"); +} + +#[test] +fn test_fixnum_and_side_exit() { + eval(" + def test(a, b) = a & b + "); + assert_contains_opcode("test", YARVINSN_opt_and); + assert_snapshot!(assert_compiles_allowing_exits(" + [ + test(2, 2), + test(0b011, 0b110), + test(true, false) + ] + "), @"[2, 2, false]"); +} + +#[test] +fn test_fixnum_or() { + eval(" + def test(a, b) = a | b + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(assert_compiles(" + [ + test(5, 3), + test(1, 2), + test(1, -4) + ] + "), @"[7, 3, -3]"); +} + +#[test] +fn test_fixnum_or_side_exit() { + eval(" + def test(a, b) = a | b + "); + assert_contains_opcode("test", YARVINSN_opt_or); + assert_snapshot!(assert_compiles_allowing_exits(" + [ + test(1, 2), + test(2, 2), + test(true, false) + ] + "), @"[3, 2, true]"); +} + +#[test] +fn test_fixnum_xor() { + assert_snapshot!(inspect(" + def test(a, b) = a ^ b + [ + test(5, 3), + test(-5, 3), + test(1, 2) + ] + "), @"[6, -8, 3]"); +} + +#[test] +fn test_fixnum_xor_side_exit() { + assert_snapshot!(inspect(" + def test(a, b) = a ^ b + [ + test(5, 3), + test(5, 3), + test(true, false) + ] + "), @"[6, 6, true]"); +} + +#[test] +fn test_fixnum_mul() { + eval(" + C = 3 + def test(n) = C * n + test(4) + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_mult); + assert_snapshot!(assert_compiles("test(4)"), @"12"); +} + +#[test] +fn test_fixnum_div() { + eval(" + C = 48 + def test(n) = C / n + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(assert_compiles("test(4)"), @"12"); +} + +#[test] +fn test_fixnum_floor() { + eval(" + C = 3 + def test(n) = C / n + test(4) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(assert_compiles("test(4)"), @"0"); +} + +#[test] +fn test_opt_not() { + eval(" + def test(obj) = !obj + "); + assert_contains_opcode("test", YARVINSN_opt_not); + assert_snapshot!(assert_compiles_allowing_exits("[test(nil), test(false), test(0)]"), @"[true, true, false]"); +} + +#[test] +fn test_opt_regexpmatch2() { + eval(" + def test(haystack) = /needle/ =~ haystack + "); + assert_contains_opcode("test", YARVINSN_opt_regexpmatch2); + assert_snapshot!(assert_compiles(r#"[test("kneedle"), test("")]"#), @"[1, nil]"); +} + +#[test] +fn test_opt_ge() { + eval(" + def test(a, b) = a >= b + test(2, 3) # profile opt_ge + "); + assert_contains_opcode("test", YARVINSN_opt_ge); + assert_snapshot!(assert_compiles("[test(0, 1), test(0, 0), test(1, 0)]"), @"[false, true, true]"); +} + +#[test] +fn test_opt_new_does_not_push_frame() { + eval(" + class Foo + attr_reader :backtrace + def initialize + @backtrace = caller + end + end + def test = Foo.new + test + "); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(assert_compiles(" + foo = test + foo.backtrace.find { |frame| frame.include?('Class#new') } + "), @"nil"); +} + +#[test] +fn test_opt_new_with_redefined() { + eval(r#" + class Foo + def self.new = "foo" + def initialize = raise("unreachable") + end + def test = Foo.new + test + "#); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(assert_compiles(r#"test"#), @r#""foo""#); +} + +#[test] +fn test_opt_new_invalidate_new() { + eval(r#" + class Foo; end + def test = Foo.new + test + "#); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(assert_compiles(r#" + result = [test.class.name] + def Foo.new = "foo" + result << test + "#), @r#"["Foo", "foo"]"#); +} + +#[test] +fn test_opt_newarray_send_include_p() { + eval(" + def test(x) + [:y, 1, Object.new].include?(x) + end + test(1) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles("[test(1), test(\"n\")]"), @"[true, false]"); +} + +#[test] +fn test_opt_newarray_send_include_p_redefined() { + eval(" + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + def test(x) + [:y, 1, Object.new].include?(x) + end + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles_allowing_exits(" + def test(x) + [:y, 1, Object.new].include?(x) + end + test(1) + [test(1), test(\"n\")] + "), @"[:true, :false]"); +} + +#[test] +fn test_opt_duparray_send_include_p() { + eval(" + def test(x) + [:y, 1].include?(x) + end + test(1) + "); + assert_contains_opcode("test", YARVINSN_opt_duparray_send); + assert_snapshot!(assert_compiles("[test(1), test(\"n\")]"), @"[true, false]"); +} + +#[test] +fn test_opt_duparray_send_include_p_redefined() { + eval(" + class Array + alias_method :old_include?, :include? + def include?(x) + old_include?(x) ? :true : :false + end + end + def test(x) + [:y, 1].include?(x) + end + "); + assert_contains_opcode("test", YARVINSN_opt_duparray_send); + assert_snapshot!(assert_compiles_allowing_exits(" + def test(x) + [:y, 1].include?(x) + end + test(1) + [test(1), test(\"n\")] + "), @"[:true, :false]"); +} + +#[test] +fn test_opt_newarray_send_pack() { + eval(r#" + def test(num) + [num].pack('C') + end + test(65) + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles(r#" + [test(65), test(66), test(67)] + "#), @r#"["A", "B", "C"]"#); +} + +#[test] +fn test_opt_newarray_send_pack_redefined() { + eval(r#" + class Array + alias_method :old_pack, :pack + def pack(fmt, buffer: nil) + "override:#{old_pack(fmt, buffer: buffer)}" + end + end + def test(num) + [num].pack('C') + end + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles_allowing_exits(r#" + [test(65), test(66), test(67)] + "#), @r#"["override:A", "override:B", "override:C"]"#); +} + +#[test] +fn test_opt_newarray_send_pack_buffer() { + eval(r#" + def test(num, buffer) + [num].pack('C', buffer:) + end + test(65, "") + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles(r#" + buf = "" + [test(65, buf), test(66, buf), test(67, buf), buf] + "#), @r#"["ABC", "ABC", "ABC", "ABC"]"#); +} + +#[test] +fn test_opt_newarray_send_pack_buffer_redefined() { + eval(r#" + class Array + alias_method :old_pack, :pack + def pack(fmt, buffer: nil) + old_pack(fmt, buffer: buffer) + "b" + end + end + def test(num, buffer) + [num].pack('C', buffer:) + end + "#); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles_allowing_exits(r#" + def test(num, buffer) + [num].pack('C', buffer:) + end + buf = "" + test(65, buf) + buf = "" + [test(65, buf), buf] + "#), @r#"["b", "A"]"#); +} + +#[test] +fn test_opt_newarray_send_hash() { + eval(" + def test(x) + [1, 2, x].hash + end + test(20) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles("test(20).class"), @"Integer"); +} + +#[test] +fn test_opt_newarray_send_hash_redefined() { + eval(" + Array.class_eval { def hash = 42 } + def test(x) + [1, 2, x].hash + end + test(20) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles_allowing_exits("test(20)"), @"42"); +} + +#[test] +fn test_opt_newarray_send_max() { + eval(" + def test(a,b) = [a,b].max + test(10, 20) + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles("[test(10, 20), test(40, 30)]"), @"[20, 40]"); +} + +#[test] +fn test_opt_newarray_send_max_redefined() { + eval(" + class Array + alias_method :old_max, :max + def max + old_max * 2 + end + end + def test(a,b) = [a,b].max + "); + assert_contains_opcode("test", YARVINSN_opt_newarray_send); + assert_snapshot!(assert_compiles_allowing_exits(" + def test(a,b) = [a,b].max + test(15, 30) + [test(15, 30), test(45, 35)] + "), @"[60, 90]"); +} + +#[test] +fn test_new_hash_empty() { + eval(" + def test = {} + test + "); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(assert_compiles("test"), @"{}"); +} + +#[test] +fn test_new_hash_nonempty() { + eval(r#" + def test + key = "key" + value = "value" + num = 42 + result = 100 + {key => value, num => result} + end + test + "#); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(assert_compiles(r#"test"#), @r#"{"key" => "value", 42 => 100}"#); +} + +#[test] +fn test_new_hash_single_key_value() { + eval(r#" + def test = {"key" => "value"} + test + "#); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(assert_compiles(r#"test"#), @r#"{"key" => "value"}"#); +} + +#[test] +fn test_new_hash_with_computation() { + eval(r#" + def test(a, b) + {"sum" => a + b, "product" => a * b} + end + test(2, 3) + "#); + assert_contains_opcode("test", YARVINSN_newhash); + assert_snapshot!(assert_compiles(r#"test(2, 3)"#), @r#"{"sum" => 5, "product" => 6}"#); +} + +#[test] +fn test_new_hash_with_user_defined_hash_method() { + assert_snapshot!(inspect(r#" + class CustomKey + attr_reader :val + def initialize(val) + @val = val + end + def hash + @val.hash + end + def eql?(other) + other.is_a?(CustomKey) && @val == other.val + end + end + def test + key = CustomKey.new("key") + hash = {key => "value"} + hash[key] == "value" + end + test + test + "#), @"true"); +} + +#[test] +fn test_new_hash_with_user_hash_method_exception() { + assert_snapshot!(inspect(r#" + class BadKey + def hash + raise "Hash method failed!" + end + end + def test + key = BadKey.new + {key => "value"} + end + begin + test + rescue => e + e.class + end + begin + test + rescue => e + e.class + end + "#), @"RuntimeError"); +} + +#[test] +fn test_new_hash_with_user_eql_method_exception() { + assert_snapshot!(inspect(r#" + class BadKey + def hash + 42 + end + def eql?(other) + raise "Eql method failed!" + end + end + def test + key1 = BadKey.new + key2 = BadKey.new + {key1 => "value1", key2 => "value2"} + end + begin + test + rescue => e + e.class + end + begin + test + rescue => e + e.class + end + "#), @"RuntimeError"); +} + +#[test] +fn test_opt_hash_freeze() { + eval(" + def test = {}.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(assert_compiles(" + result = [test] + class Hash + def freeze = 5 + end + result << test + "), @"[{}, 5]"); +} + +#[test] +fn test_opt_hash_freeze_rewritten() { + eval(" + class Hash + def freeze = 5 + end + def test = {}.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_hash_freeze); + assert_snapshot!(assert_compiles_allowing_exits("test"), @"5"); +} + +#[test] +fn test_opt_aset_hash() { + eval(" + def test(h, k, v) + h[k] = v + end + test({}, :key, 42) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(assert_compiles("h = {}; test(h, :key, 42); h[:key]"), @"42"); +} + +#[test] +fn test_opt_aset_hash_returns_value() { + assert_snapshot!(inspect(" + def test(h, k, v) + h[k] = v + end + test({}, :key, 100) + test({}, :key, 100) + "), @"100"); +} + +#[test] +fn test_opt_aset_hash_string_key() { + assert_snapshot!(inspect(r#" + def test(h, k, v) + h[k] = v + end + h = {} + test(h, "foo", "bar") + test(h, "foo", "bar") + h["foo"] + "#), @r#""bar""#); +} + +#[test] +fn test_opt_aset_hash_subclass() { + assert_snapshot!(inspect(" + class MyHash < Hash; end + def test(h, k, v) + h[k] = v + end + h = MyHash.new + test(h, :key, 42) + test(h, :key, 42) + h[:key] + "), @"42"); +} + +#[test] +fn test_opt_aset_hash_too_few_args() { + assert_snapshot!(inspect(r#" + def test(h) + h.[]= 123 + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + "#), @r#""ArgumentError""#); +} + +#[test] +fn test_opt_aset_hash_too_many_args() { + assert_snapshot!(inspect(r#" + def test(h) + h[:a, :b] = :c + rescue ArgumentError + "ArgumentError" + end + test({}) + test({}) + "#), @r#""ArgumentError""#); +} + +#[test] +fn test_opt_ary_freeze() { + eval(" + def test = [].freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(assert_compiles(" + result = [test] + class Array + def freeze = 5 + end + result << test + "), @"[[], 5]"); +} + +#[test] +fn test_opt_ary_freeze_rewritten() { + eval(" + class Array + def freeze = 5 + end + def test = [].freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_ary_freeze); + assert_snapshot!(assert_compiles_allowing_exits("test"), @"5"); +} + +#[test] +fn test_opt_str_freeze() { + eval(" + def test = ''.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(assert_compiles(r#" + result = [test] + class String + def freeze = 5 + end + result << test + "#), @r#"["", 5]"#); +} + +#[test] +fn test_opt_str_freeze_rewritten() { + eval(" + class String + def freeze = 5 + end + def test = ''.freeze + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_freeze); + assert_snapshot!(assert_compiles_allowing_exits("test"), @"5"); +} + +#[test] +fn test_opt_str_uminus() { + eval(" + def test = -'' + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(assert_compiles(r#" + result = [test] + class String + def -@ = 5 + end + result << test + "#), @r#"["", 5]"#); +} + +#[test] +fn test_opt_str_uminus_rewritten() { + eval(" + class String + def -@ = 5 + end + def test = -'' + test + "); + assert_contains_opcode("test", YARVINSN_opt_str_uminus); + assert_snapshot!(assert_compiles_allowing_exits("test"), @"5"); +} + +#[test] +fn test_new_array_empty() { + eval(" + def test = [] + test + "); + assert_contains_opcode("test", YARVINSN_newarray); + assert_snapshot!(assert_compiles("test"), @"[]"); +} + +#[test] +fn test_new_array_nonempty() { + assert_snapshot!(inspect(" + def a = 5 + def test = [a] + test + test + "), @"[5]"); +} + +#[test] +fn test_new_array_order() { + assert_snapshot!(inspect(" + def a = 3 + def b = 2 + def c = 1 + def test = [a, b, c] + test + test + "), @"[3, 2, 1]"); +} + +#[test] +fn test_array_dup() { + assert_snapshot!(inspect(" + def test = [1,2,3] + test + test + "), @"[1, 2, 3]"); +} + +#[test] +fn test_array_fixnum_aref() { + eval(" + def test(x) = [1,2,3][x] + test(2) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles("test(2)"), @"3"); +} + +#[test] +fn test_array_fixnum_aref_negative_index() { + eval(" + def test(x) = [1,2,3][x] + test(-1) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles("test(-1)"), @"3"); +} + +#[test] +fn test_array_fixnum_aref_out_of_bounds_positive() { + eval(" + def test(x) = [1,2,3][x] + test(10) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles_allowing_exits("test(10)"), @"nil"); +} + +#[test] +fn test_array_fixnum_aref_out_of_bounds_negative() { + eval(" + def test(x) = [1,2,3][x] + test(-10) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles_allowing_exits("test(-10)"), @"nil"); +} + +#[test] +fn test_array_fixnum_aref_array_subclass() { + eval(" + class MyArray < Array; end + def test(arr, idx) = arr[idx] + test(MyArray[1,2,3], 2) + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles("test(MyArray[1,2,3], 2)"), @"3"); +} + +#[test] +fn test_array_aref_non_fixnum_index() { + assert_snapshot!(inspect(r#" + def test(arr, idx) = arr[idx] + test([1,2,3], 1) + test([1,2,3], 1) + begin + test([1,2,3], "1") + rescue => e + e.class + end + "#), @"TypeError"); +} + +#[test] +fn test_array_fixnum_aset() { + eval(" + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 2) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(assert_compiles("arr = [1,2,3]; test(arr, 2); arr"), @"[1, 2, 7]"); +} + +#[test] +fn test_array_fixnum_aset_returns_value() { + eval(" + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 2) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(assert_compiles("test([1,2,3], 2)"), @"7"); +} + +#[test] +fn test_array_fixnum_aset_out_of_bounds() { + assert_snapshot!(inspect(" + def test(arr) + arr[5] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + "), @"[1, 2, 3, nil, nil, 7]"); +} + +#[test] +fn test_array_fixnum_aset_negative_index() { + assert_snapshot!(inspect(" + def test(arr) + arr[-1] = 7 + end + arr = [1,2,3] + test(arr) + arr = [1,2,3] + test(arr) + arr + "), @"[1, 2, 7]"); +} + +#[test] +fn test_array_fixnum_aset_shared() { + assert_snapshot!(inspect(" + def test(arr, idx, val) + arr[idx] = val + end + arr = (0..50).to_a + test(arr, 0, -1) + test(arr, 1, -2) + shared = arr[10, 20] + test(shared, 0, 999) + [arr[10], shared[0], arr[0], arr[1]] + "), @"[10, 999, -1, -2]"); +} + +#[test] +fn test_array_fixnum_aset_frozen() { + assert_snapshot!(inspect(" + def test(arr, idx, val) + arr[idx] = val + end + arr = [1,2,3] + test(arr, 1, 9) + test(arr, 1, 9) + arr.freeze + begin + test(arr, 1, 9) + rescue => e + e.class + end + "), @"FrozenError"); +} + +#[test] +fn test_array_fixnum_aset_array_subclass() { + eval(" + class MyArray < Array; end + def test(arr, idx) + arr[idx] = 7 + end + test(MyArray.new, 0) + "); + assert_contains_opcode("test", YARVINSN_opt_aset); + assert_snapshot!(assert_compiles("arr = MyArray.new; test(arr, 0); arr[0]"), @"7"); +} + +#[test] +fn test_array_aset_non_fixnum_index() { + assert_snapshot!(inspect(r#" + def test(arr, idx) + arr[idx] = 7 + end + test([1,2,3], 0) + test([1,2,3], 0) + begin + test([1,2,3], "0") + rescue => e + e.class + end + "#), @"TypeError"); +} + +#[test] +fn test_empty_array_pop() { + assert_snapshot!(inspect(" + def test(arr) = arr.pop + test([]) + test([]) + "), @"nil"); +} + +#[test] +fn test_array_pop_no_arg() { + assert_snapshot!(inspect(" + def test(arr) = arr.pop + test([32, 33, 42]) + test([32, 33, 42]) + "), @"42"); +} + +#[test] +fn test_array_pop_arg() { + assert_snapshot!(inspect(" + def test(arr) = arr.pop(2) + test([32, 33, 42]) + test([32, 33, 42]) + "), @"[33, 42]"); +} + +#[test] +fn test_new_range_inclusive() { + assert_snapshot!(inspect(" + def test(a, b) = a..b + test(1, 5) + test(1, 5) + "), @"1..5"); +} + +#[test] +fn test_new_range_exclusive() { + assert_snapshot!(inspect(" + def test(a, b) = a...b + test(1, 5) + test(1, 5) + "), @"1...5"); +} + +#[test] +fn test_new_range_with_literal() { + assert_snapshot!(inspect(" + def test(n) = n..10 + test(3) + test(3) + "), @"3..10"); +} + +#[test] +fn test_new_range_fixnum_both_literals_inclusive() { + eval(" + def test() + a = 2 + (1..a) + end + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(assert_compiles("test; test"), @"1..2"); +} + +#[test] +fn test_new_range_fixnum_both_literals_exclusive() { + eval(" + def test() + a = 2 + (1...a) + end + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(assert_compiles("test; test"), @"1...2"); +} + +#[test] +fn test_new_range_fixnum_low_literal_inclusive() { + eval(" + def test(a) = (1..a) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(assert_compiles("test(2); test(3)"), @"1..3"); +} + +#[test] +fn test_new_range_fixnum_low_literal_exclusive() { + eval(" + def test(a) = (1...a) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(assert_compiles("test(2); test(3)"), @"1...3"); +} + +#[test] +fn test_new_range_fixnum_high_literal_inclusive() { + eval(" + def test(a) = (a..10) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(assert_compiles("test(2); test(3)"), @"3..10"); +} + +#[test] +fn test_new_range_fixnum_high_literal_exclusive() { + eval(" + def test(a) = (a...10) + "); + assert_contains_opcode("test", YARVINSN_newrange); + assert_snapshot!(assert_compiles("test(2); test(3)"), @"3...10"); +} + +#[test] +fn test_if() { + assert_snapshot!(inspect(" + def test(n) + if n < 5 + 0 + end + end + test(3) + [test(3), test(7)] + "), @"[0, nil]"); +} + +#[test] +fn test_if_else() { + assert_snapshot!(inspect(" + def test(n) + if n < 5 + 0 + else + 1 + end + end + test(3) + [test(3), test(7)] + "), @"[0, 1]"); +} + +#[test] +fn test_if_else_params() { + assert_snapshot!(inspect(" + def test(n, a, b) + if n < 5 + a + else + b + end + end + test(3, 1, 2) + [test(3, 1, 2), test(7, 10, 20)] + "), @"[1, 20]"); +} + +#[test] +fn test_if_else_nested() { + assert_snapshot!(inspect(" + def test(a, b, c, d, e) + if 2 < a + if a < 4 + b + else + c + end + else + if a < 0 + d + else + e + end + end + end + test(-1, 1, 2, 3, 4) + [ + test(-1, 1, 2, 3, 4), + test( 0, 5, 6, 7, 8), + test( 3, 9, 10, 11, 12), + test( 5, 13, 14, 15, 16), + ] + "), @"[3, 8, 9, 14]"); +} + +#[test] +fn test_if_else_chained() { + assert_snapshot!(inspect(" + def test(a) + (if 2 < a then 1 else 2 end) + (if a < 4 then 10 else 20 end) + end + test(0) + [test(0), test(3), test(5)] + "), @"[12, 11, 21]"); +} + +#[test] +fn test_if_elsif_else() { + assert_snapshot!(inspect(" + def test(n) + if n < 5 + 0 + elsif 8 < n + 1 + else + 2 + end + end + test(3) + [test(3), test(7), test(9)] + "), @"[0, 2, 1]"); +} + +#[test] +fn test_ternary_operator() { + assert_snapshot!(inspect(" + def test(n, a, b) + n < 5 ? a : b + end + test(3, 1, 2) + [test(3, 1, 2), test(7, 10, 20)] + "), @"[1, 20]"); +} + +#[test] +fn test_ternary_operator_nested() { + assert_snapshot!(inspect(" + def test(n, a, b) + (n < 5 ? a : b) + 1 + end + test(3, 1, 2) + [test(3, 1, 2), test(7, 10, 20)] + "), @"[2, 21]"); +} + +#[test] +fn test_while_loop() { + assert_snapshot!(inspect(" + def test(n) + i = 0 + while i < n + i = i + 1 + end + i + end + test(10) + test(10) + "), @"10"); +} + +#[test] +fn test_while_loop_chain() { + assert_snapshot!(inspect(" + def test(n) + i = 0 + while i < n + i = i + 1 + end + while i < n * 10 + i = i * 3 + end + i + end + test(5) + [test(5), test(10)] + "), @"[135, 270]"); +} + +#[test] +fn test_while_loop_nested() { + assert_snapshot!(inspect(" + def test(n, m) + i = 0 + while i < n + j = 0 + while j < m + j += 2 + end + i += j + end + i + end + test(0, 0) + [test(0, 0), test(1, 3), test(10, 5)] + "), @"[0, 4, 12]"); +} + +#[test] +fn test_while_loop_if_else() { + assert_snapshot!(inspect(" + def test(n) + i = 0 + while i < n + if n >= 10 + return -1 + else + i = i + 1 + end + end + i + end + test(9) + [test(9), test(10)] + "), @"[9, -1]"); +} + +#[test] +fn test_if_while_loop() { + assert_snapshot!(inspect(" + def test(n) + i = 0 + if n < 10 + while i < n + i += 1 + end + else + while i < n + i += 3 + end + end + i + end + test(9) + [test(9), test(10)] + "), @"[9, 12]"); +} + +#[test] +fn test_live_reg_past_ccall() { + assert_snapshot!(inspect(" + def callee = 1 + def test = callee + callee + test + test + "), @"2"); +} + +#[test] +fn test_method_call() { + assert_snapshot!(inspect(" + def callee(a, b) + a - b + end + def test + callee(4, 2) + 10 + end + test + test + "), @"12"); +} + +#[test] +fn test_recursive_fact() { + assert_snapshot!(inspect(" + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + fact(0) + [fact(0), fact(3), fact(6)] + "), @"[1, 6, 720]"); +} + +#[test] +fn test_recursive_fib() { + assert_snapshot!(inspect(" + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + fib(0) + [fib(0), fib(3), fib(4)] + "), @"[0, 2, 3]"); +} + +#[test] +fn test_spilled_basic_block_args() { + assert_snapshot!(inspect(" + def test(n1, n2) + n3 = 3 + n4 = 4 + n5 = 5 + n6 = 6 + n7 = 7 + n8 = 8 + n9 = 9 + n10 = 10 + if n1 < n2 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + end + test(1, 2) + test(1, 2) + "), @"55"); +} + +#[test] +fn test_putself() { + assert_snapshot!(inspect(" + class Integer + def minus(a) + self - a + end + end + 5.minus(2) + 5.minus(2) + "), @"3"); +} + +#[test] +fn test_getinstancevariable_nil() { + assert_snapshot!(inspect(" + def test() = @foo + test() + test() + "), @"nil"); +} + +#[test] +fn test_getinstancevariable() { + assert_snapshot!(inspect(" + @foo = 3 + def test() = @foo + test() + test() + "), @"3"); +} + +#[test] +fn test_getinstancevariable_miss() { + assert_snapshot!(inspect(" + class C + def foo + @foo + end + def foo_then_bar + @foo = 1 + @bar = 2 + end + def bar_then_foo + @bar = 3 + @foo = 4 + end + end + o1 = C.new + o1.foo_then_bar + result = [] + result << o1.foo + result << o1.foo + o2 = C.new + o2.bar_then_foo + result << o2.foo + result + "), @"[1, 1, 4]"); +} + +#[test] +fn test_setinstancevariable() { + assert_snapshot!(inspect(" + def test() = @foo = 1 + test() + test() + @foo + "), @"1"); +} + +#[test] +fn test_getclassvariable() { + assert_snapshot!(inspect(" + class Foo + def self.test = @@x + end + Foo.class_variable_set(:@@x, 42) + Foo.test() + Foo.test() + "), @"42"); +} + +#[test] +fn test_getclassvariable_raises() { + assert_snapshot!(inspect(r#" + class Foo + def self.test = @@x + end + begin + Foo.test + Foo.test + rescue NameError => e + e.message + end + "#), @r#""uninitialized class variable @@x in Foo""#); +} + +#[test] +fn test_setclassvariable() { + assert_snapshot!(inspect(" + class Foo + def self.test = @@x = 42 + end + Foo.test() + Foo.test() + Foo.class_variable_get(:@@x) + "), @"42"); +} + +#[test] +fn test_setclassvariable_raises() { + assert_snapshot!(inspect(r#" + class Foo + def self.test = @@x = 42 + freeze + end + begin + Foo.test + Foo.test + rescue FrozenError => e + e.message + end + "#), @r#""can't modify frozen Class: Foo""#); +} + +#[test] +fn test_attr_reader() { + eval(" + class C + attr_reader :foo + def initialize + @foo = 4 + end + end + def test(c) = c.foo + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("c = C.new; [test(c), test(c)]"), @"[4, 4]"); +} + +#[test] +fn test_attr_accessor_getivar() { + eval(" + class C + attr_accessor :foo + def initialize + @foo = 4 + end + end + def test(c) = c.foo + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("c = C.new; [test(c), test(c)]"), @"[4, 4]"); +} + +#[test] +fn test_getivar_t_data_then_string() { + // This is a regression test for a type confusion miscomp where + // we end up reading the fields object using an offset off of a + // string, assuming that it has a the same layout as a T_DATA object. + // At the time of writing the fields object of strings are stored + // in a global table, out-of-line of each string. + // The string and the thread end up sharing one shape ID. + set_call_threshold(2); + eval(r#" + module GetThousand + def test = @var1000 + end + class Thread + include GetThousand + end + class String + include GetThousand + end + OBJ = Thread.new { } + OBJ.join + STR = +'' + (0..1000).each do |i| + ivar_name = :"@var#{i}" + OBJ.instance_variable_set(ivar_name, i) + STR.instance_variable_set(ivar_name, i) + end + OBJ.test; OBJ.test # profile and compile for Thread (T_DATA) + "#); + assert_snapshot!(assert_compiles_allowing_exits("[STR.test, STR.test]"), @"[1000, 1000]"); +} + +#[test] +fn test_getivar_t_object_then_string() { + // This test construct an object and a string that have the same set of ivars. + // They wouldn't share the same shape ID, though, and we rely on this fact in + // our guards. + set_call_threshold(2); + eval(r#" + module GetThousand + def test = @var1000 + end + class MyObject + include GetThousand + end + class String + include GetThousand + end + OBJ = MyObject.new + STR = +'' + (0..1000).each do |i| + ivar_name = :"@var#{i}" + OBJ.instance_variable_set(ivar_name, i) + STR.instance_variable_set(ivar_name, i) + end + OBJ.test; OBJ.test # profile and compile for MyObject + "#); + assert_snapshot!(assert_compiles_allowing_exits("[STR.test, STR.test]"), @"[1000, 1000]"); +} + +#[test] +fn test_getivar_t_class_then_string() { + // This is a regression test for a type confusion miscomp where + // we end up reading the fields object using an offset off of a + // string, assuming that it has a the same layout as a T_CLASS object. + // At the time of writing the fields object of strings are stored + // in a global table, out-of-line of each string. + // The string and the class end up sharing one shape ID. + set_call_threshold(2); + eval(r#" + module GetThousand + def test = @var1000 + end + class MyClass + extend GetThousand + end + class String + include GetThousand + end + STR = +'' + (0..1000).each do |i| + ivar_name = :"@var#{i}" + MyClass.instance_variable_set(ivar_name, i) + STR.instance_variable_set(ivar_name, i) + end + p MyClass.test; p MyClass.test # profile and compile for MyClass + p STR.test + "#); + assert_snapshot!(assert_compiles_allowing_exits("[STR.test, STR.test]"), @"[1000, 1000]"); +} + + +#[test] +fn test_attr_accessor_setivar() { + eval(" + class C + attr_accessor :foo + def initialize + @foo = 4 + end + end + def test(c) + c.foo = 5 + c.foo + end + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("c = C.new; [test(c), test(c)]"), @"[5, 5]"); +} + +#[test] +fn test_attr_writer() { + eval(" + class C + attr_writer :foo + def initialize + @foo = 4 + end + def get_foo = @foo + end + def test(c) + c.foo = 5 + c.get_foo + end + test(C.new) + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("c = C.new; [test(c), test(c)]"), @"[5, 5]"); +} + +#[test] +fn test_getconstant() { + eval(" + class Foo + CONST = 1 + end + def test(klass) + klass::CONST + end + test(Foo) + "); + assert_contains_opcode("test", YARVINSN_getconstant); + assert_snapshot!(assert_compiles("test(Foo)"), @"1"); +} + +#[test] +fn test_expandarray_no_splat() { + eval(" + def test(o) + a, b = o + [a, b] + end + test [3, 4] + "); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(assert_compiles("test [3, 4]"), @"[3, 4]"); +} + +#[test] +fn test_expandarray_splat() { + eval(" + def test(o) + a, *b = o + [a, b] + end + test [3, 4] + "); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(assert_compiles_allowing_exits("test [3, 4]"), @"[3, [4]]"); +} + +#[test] +fn test_expandarray_splat_post() { + eval(" + def test(o) + a, *b, c = o + [a, b, c] + end + test [3, 4, 5] + "); + assert_contains_opcode("test", YARVINSN_expandarray); + assert_snapshot!(assert_compiles_allowing_exits("test [3, 4, 5]"), @"[3, [4], 5]"); +} + +#[test] +fn test_constant_invalidation() { + eval(" + class C; end + def test = C + test + test + C = 123 + "); + assert_contains_opcode("test", YARVINSN_opt_getconstant_path); + assert_snapshot!(assert_compiles("test"), @"123"); +} + +#[test] +fn test_constant_path_invalidation() { + eval(" + module A + module B; end + end + module Foo + C = 'Foo::C' + end + A::B = Foo + def test = A::B::C + "); + assert_contains_opcode("test", YARVINSN_opt_getconstant_path); + assert_snapshot!(assert_compiles(r#" + module A + module B; end + end + module Foo + C = "Foo::C" + end + module Bar + C = "Bar::C" + end + A::B = Foo + def test = A::B::C + result = [] + result << test + result << test + A::B = Bar + result << test + result + "#), @r#"["Foo::C", "Foo::C", "Bar::C"]"#); +} + +#[test] +fn test_dupn() { + eval(" + def test(array) = (array[1, 2] ||= :rhs) + test([1, 1]) + "); + assert_contains_opcode("test", YARVINSN_dupn); + assert_snapshot!(assert_compiles_allowing_exits(" + one = [1, 1] + start_empty = [] + [test(one), one, test(start_empty), start_empty] + "), @"[[1], [1, 1], :rhs, [nil, :rhs]]"); +} + +#[test] +fn test_bop_invalidation() { + assert_snapshot!(inspect(r#" + def test + eval("class Integer; def +(_) = 100; end") + 1 + 2 + end + test + test + "#), @"100"); +} + +#[test] +fn test_defined_with_defined_values() { + eval(" + class Foo; end + def bar; end + $ruby = 1 + def test = [defined?(Foo), defined?(bar), defined?($ruby)] + test + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(assert_compiles("test"), @r#"["constant", "method", "global-variable"]"#); +} + +#[test] +fn test_defined_with_undefined_values() { + eval(" + def test = [defined?(FooUndef), defined?(bar_undef), defined?($ruby_undef)] + test + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(assert_compiles("test"), @"[nil, nil, nil]"); +} + +#[test] +fn test_defined_with_method_call() { + eval(r#" + def test = [defined?("x".reverse(1)), defined?("x".reverse(1).reverse)] + test + "#); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(assert_compiles(r#"test"#), @r#"["method", nil]"#); +} + +#[test] +fn test_defined_method_raise() { + assert_snapshot!(inspect(r#" + class C + def assert_equal expected, actual + if expected != actual + raise "NO" + end + end + def test_defined_method + assert_equal(nil, defined?("x".reverse(1).reverse)) + end + end + c = C.new + result = [] + result << c.test_defined_method + result << c.test_defined_method + result << c.test_defined_method + result + "#), @"[nil, nil, nil]"); +} + +#[test] +fn test_defined_yield() { + eval(" + def test = defined?(yield) + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(assert_compiles("[test, test, test{}]"), @r#"[nil, nil, "yield"]"#); +} + +#[test] +fn test_defined_yield_from_block() { + assert_snapshot!(inspect(" + def test + yield_self { yield_self { defined?(yield) } } + end + [test, test, test{}] + "), @r#"[nil, nil, "yield"]"#); +} + +#[test] +fn test_block_given_p() { + assert_snapshot!(inspect(" + def test = block_given? + [test, test, test{}] + "), @"[false, false, true]"); +} + +#[test] +fn test_block_given_p_from_block() { + assert_snapshot!(inspect(" + def test + yield_self { yield_self { block_given? } } + end + [test, test, test{}] + "), @"[false, false, true]"); +} + +#[test] +fn test_invokeblock_without_block_after_jit_call() { + assert_snapshot!(inspect(r#" + def test(*arr, &b) + arr.class + yield + end + test { } + begin + test + rescue => e + e.message + end + "#), @r#""no block given (yield)""#); +} + +#[test] +fn test_putspecialobject_vm_core_and_cbase() { + eval(" + def test + alias bar test + 10 + end + test + "); + assert_contains_opcode("test", YARVINSN_putspecialobject); + assert_snapshot!(assert_compiles("bar"), @"10"); +} + +#[test] +fn test_putspecialobject_const_base() { + assert_snapshot!(inspect(" + Foo = 1 + def test = Foo + test + test + "), @"1"); +} + +#[test] +fn test_branchnil() { + eval(" + def test(x) + x&.succ + end + test(0) + "); + assert_contains_opcode("test", YARVINSN_branchnil); + assert_snapshot!(assert_compiles("[test(1), test(nil)]"), @"[2, nil]"); +} + +#[test] +fn test_nil_nil() { + eval(" + def test = nil.nil? + test + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test"), @"true"); +} + +#[test] +fn test_non_nil_nil() { + eval(" + def test = 1.nil? + test + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test"), @"false"); +} + +#[test] +fn test_getspecial_last_match() { + eval(r#" + def test(str) + str =~ /hello/ + $& + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @r#""hello""#); +} + +#[test] +fn test_getspecial_match_pre() { + eval(r#" + def test(str) + str =~ /world/ + $` + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @r#""hello ""#); +} + +#[test] +fn test_getspecial_match_post() { + eval(r#" + def test(str) + str =~ /hello/ + $' + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @r#"" world""#); +} + +#[test] +fn test_getspecial_match_last_group() { + eval(r#" + def test(str) + str =~ /(hello) (world)/ + $+ + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @r#""world""#); +} + +#[test] +fn test_getspecial_numbered_match_1() { + eval(r#" + def test(str) + str =~ /(hello) (world)/ + $1 + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @r#""hello""#); +} + +#[test] +fn test_getspecial_numbered_match_2() { + eval(r#" + def test(str) + str =~ /(hello) (world)/ + $2 + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @r#""world""#); +} + +#[test] +fn test_getspecial_numbered_match_nonexistent() { + eval(r#" + def test(str) + str =~ /(hello)/ + $2 + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @"nil"); +} + +#[test] +fn test_getspecial_no_match() { + eval(r#" + def test(str) + str =~ /xyz/ + $& + end + test("hello world") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("hello world")"#), @"nil"); +} + +#[test] +fn test_getspecial_complex_pattern() { + eval(r#" + def test(str) + str =~ /(\d+)/ + $1 + end + test("abc123def") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("abc123def")"#), @r#""123""#); +} + +#[test] +fn test_getspecial_multiple_groups() { + eval(r#" + def test(str) + str =~ /(\d+)-(\d+)/ + $2 + end + test("123-456") + "#); + assert_contains_opcode("test", YARVINSN_getspecial); + assert_snapshot!(assert_compiles(r#"test("123-456")"#), @r#""456""#); +} + +// In a JIT-to-JIT call, gen_push_frame writes JIT_RETURN_POISON to the +// callee's cfp->jit_return (runtime_checks builds). On the *first* such +// call the function stub trampoline clears jit_return to NULL, so the +// crash only manifests on the second JIT-to-JIT hit when the stub has +// been patched to jump directly to the callee's JIT entry. Putting $& as +// the first C call in the callee keeps the poison live until +// gen_getspecial_symbol calls rb_backref_get → rb_vm_svar_lep → CFP_PC → +// CFP_ZJIT_FRAME, which dereferences the poison without the prep fix. +#[test] +fn test_getspecial_symbol_in_jit_to_jit_callee() { + eval(r#" + def callee = $& + def caller_method = callee + + # Warm up callee so it JITs + callee + callee + + # First call to caller_method profiles; second JITs caller_method + # and runs through the function-stub-hit path which clears + # jit_return. The third call goes through the patched stub with + # POISON intact, hitting the bug. + caller_method + caller_method + "#); + assert_contains_opcode("callee", YARVINSN_getspecial); + assert_snapshot!(assert_compiles("caller_method"), @"nil"); +} + +// Same JIT-to-JIT setup, exercising gen_getspecial_number ($N). +#[test] +fn test_getspecial_number_in_jit_to_jit_callee() { + eval(r#" + def callee = $1 + def caller_method = callee + + callee + callee + + caller_method + caller_method + "#); + assert_contains_opcode("callee", YARVINSN_getspecial); + assert_snapshot!(assert_compiles("caller_method"), @"nil"); +} + +#[test] +fn test_profile_under_nested_jit_call() { + assert_snapshot!(inspect(" + def profile + 1 + 2 + end + def jit_call(flag) + if flag + profile + end + end + def entry(flag) + jit_call(flag) + end + [entry(false), entry(false), entry(true)] + "), @"[nil, nil, 3]"); +} + +#[test] +fn test_bop_redefined() { + assert_snapshot!(inspect(" + def test + 1 + 2 + end + test + [test, Integer.class_eval { def +(_) = 100 }, test] + "), @"[3, :+, 100]"); +} + +#[test] +fn test_bop_redefined_with_adjacent_patch_points() { + assert_snapshot!(inspect(" + def test + 1 + 2 + 3 + 4 + 5 + end + test + [test, Integer.class_eval { def +(_) = 100 }, test] + "), @"[15, :+, 100]"); +} + +#[test] +fn test_method_redefined_with_top_self() { + assert_snapshot!(inspect(r#" + def foo + "original" + end + def test = foo + test; test + result1 = test + def foo + "redefined" + end + result2 = test + [result1, result2] + "#), @r#"["original", "redefined"]"#); +} + +#[test] +fn test_method_redefined_with_module() { + assert_snapshot!(inspect(r#" + module Foo + def self.foo = "original" + end + def test = Foo.foo + test + result1 = test + def Foo.foo = "redefined" + result2 = test + [result1, result2] + "#), @r#"["original", "redefined"]"#); +} + +#[test] +fn test_module_name_with_guard_passes() { + assert_snapshot!(inspect(r#" + def test(mod) + mod.name + end + test(String) + test(Integer) + "#), @r#""Integer""#); +} + +#[test] +fn test_module_name_with_guard_side_exit() { + assert_snapshot!(inspect(r#" + class MyClass + def name = "Bar" + end + def test(mod) + mod.name + end + results = [] + results << test(String) + results << test(Integer) + results << test(MyClass.new) + results + "#), @r#"["String", "Integer", "Bar"]"#); +} + +#[test] +fn test_objtostring_calls_to_s_on_non_strings() { + assert_snapshot!(inspect(r##" + results = [] + class Foo + def to_s + "foo" + end + end + def test(str) + "#{str}" + end + results << test(Foo.new) + results << test(Foo.new) + results + "##), @r#"["foo", "foo"]"#); +} + +#[test] +fn test_objtostring_rewrite_does_not_call_to_s_on_strings() { + assert_snapshot!(inspect(r##" + results = [] + class String + def to_s + "bad" + end + end + def test(foo) + "#{foo}" + end + results << test("foo") + results << test("foo") + results + "##), @r#"["foo", "foo"]"#); +} + +#[test] +fn test_objtostring_rewrite_does_not_call_to_s_on_string_subclasses() { + assert_snapshot!(inspect(r##" + results = [] + class StringSubclass < String + def to_s + "bad" + end + end + foo = StringSubclass.new("foo") + def test(str) + "#{str}" + end + results << test(foo) + results << test(foo) + results + "##), @r#"["foo", "foo"]"#); +} + +#[test] +fn test_objtostring_profiled_string_fastpath() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test('foo'); test('foo') + "##), @r#""foo""#); +} + +#[test] +fn test_objtostring_profiled_string_subclass_fastpath() { + assert_snapshot!(inspect(r##" + class MyString < String; end + def test(str) + "#{str}" + end + foo = MyString.new("foo") + test(foo); test(foo) + "##), @r#""foo""#); +} + +#[test] +fn test_objtostring_profiled_string_fastpath_exits_on_nonstring() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test('foo') + test(1) + "##), @r#""1""#); +} + +#[test] +fn test_objtostring_profiled_nonstring_calls_to_s() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test([1,2,3]); + test([1,2,3]); + "##), @r#""[1, 2, 3]""#); +} + +#[test] +fn test_objtostring_profiled_nonstring_guard_exits_when_string() { + assert_snapshot!(inspect(r##" + def test(str) + "#{str}" + end + test([1,2,3]); + test('foo'); + "##), @r#""foo""#); +} + +#[test] +fn test_string_bytesize_with_guard() { + assert_snapshot!(inspect(" + def test(str) + str.bytesize + end + test('hello') + test('world') + "), @"5"); +} + +#[test] +fn test_string_bytesize_multibyte() { + assert_snapshot!(inspect(r#" + def test(s) + s.bytesize + end + test("💎") + test("💎") + "#), @"4"); +} + +#[test] +fn test_nil_value_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(nil) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test(nil)"), @"true"); +} + +#[test] +fn test_nil_value_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(nil) + test(nil) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(1)"), @"false"); +} + +#[test] +fn test_true_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(true) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test(true)"), @"false"); +} + +#[test] +fn test_true_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(true) + test(true) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(nil)"), @"true"); +} + +#[test] +fn test_false_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(false) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test(false)"), @"false"); +} + +#[test] +fn test_false_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(false) + test(false) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(nil)"), @"true"); +} + +#[test] +fn test_integer_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(1) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test(2)"), @"false"); +} + +#[test] +fn test_integer_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(1) + test(2) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(nil)"), @"true"); +} + +#[test] +fn test_float_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(1.0) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test(2.0)"), @"false"); +} + +#[test] +fn test_float_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(1.0) + test(2.0) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(nil)"), @"true"); +} + +#[test] +fn test_symbol_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(:foo) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles("test(:bar)"), @"false"); +} + +#[test] +fn test_symbol_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(:foo) + test(:bar) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(nil)"), @"true"); +} + +#[test] +fn test_class_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(String) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(Integer)"), @"false"); +} + +#[test] +fn test_class_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(String) + test(Integer) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(nil)"), @"true"); +} + +#[test] +fn test_module_nil_opt_with_guard() { + eval(" + def test(val) = val.nil? + test(Enumerable) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(Kernel)"), @"false"); +} + +#[test] +fn test_module_nil_opt_with_guard_side_exit() { + eval(" + def test(val) = val.nil? + test(Enumerable) + test(Kernel) + "); + assert_contains_opcode("test", YARVINSN_opt_nil_p); + assert_snapshot!(assert_compiles_allowing_exits("test(nil)"), @"true"); +} + +#[test] +fn test_basic_object_guard_works_with_immediate() { + assert_snapshot!(inspect(" + class Foo; end + def test(val) = val.class + test(Foo.new) + test(Foo.new) + test(nil) + "), @"NilClass"); +} + +#[test] +fn test_basic_object_guard_works_with_false() { + assert_snapshot!(inspect(" + class Foo; end + def test(val) = val.class + test(Foo.new) + test(Foo.new) + test(false) + "), @"FalseClass"); +} + +#[test] +fn test_string_concat() { + eval(r##" + def test = "#{1}#{2}#{3}" + test + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(assert_compiles(r##"test"##), @r#""123""#); +} + +#[test] +fn test_string_concat_empty() { + eval(r##" + def test = "#{}" + test + "##); + assert_contains_opcode("test", YARVINSN_concatstrings); + assert_snapshot!(assert_compiles(r##"test"##), @r#""""#); +} + +#[test] +fn test_regexp_interpolation() { + eval(r##" + def test = /#{1}#{2}#{3}/ + test + "##); + assert_contains_opcode("test", YARVINSN_toregexp); + assert_snapshot!(assert_compiles(r##"test"##), @"/123/"); +} + +#[test] +fn test_new_range_non_leaf() { + assert_snapshot!(inspect(" + def jit_entry(v) = make_range_then_exit(v) + def make_range_then_exit(v) + range = (v..1) + super rescue range + end + jit_entry(0) + jit_entry(0) + jit_entry(0/1r) + "), @"(0/1)..1"); +} + +#[test] +fn test_raise_in_second_argument() { + assert_snapshot!(inspect(" + def write(hash, key) + hash[key] = raise rescue true + hash + end + write({}, :warmup) + write({}, :ok) + "), @"{ok: true}"); +} + +#[test] +fn test_struct_set() { + assert_snapshot!(inspect(" + C = Struct.new(:foo).new(1) + def test + C.foo = Object.new + 42 + end + r = [test, test] + C.freeze + r << begin + test + rescue FrozenError + :frozen_error + end + "), @"[42, 42, :frozen_error]"); +} + +#[test] +fn test_opt_case_dispatch() { + eval(" + def test(x) + case x + when :foo + true + else + false + end + end + test(:warmup) + "); + assert_contains_opcode("test", YARVINSN_opt_case_dispatch); + assert_snapshot!(assert_compiles("[test(:foo), test(1)]"), @"[true, false]"); +} + +#[test] +fn test_checkmatch_case() { + eval(r#" + def test(o) + case o + in Integer + 1 + else + 2 + end + end + "#); + assert_contains_opcode("test", YARVINSN_checkmatch); + assert_snapshot!(assert_compiles(r#"[test(1), test(2), test("3")]"#), @"[1, 1, 2]"); +} + +#[test] +fn test_checkmatch_case_splat_array() { + eval(r#" + def test(o) + case o + when *[1, 2] + 1 + else + 2 + end + end + "#); + assert_contains_opcode("test", YARVINSN_checkmatch); + assert_snapshot!(assert_compiles("[test(1), test(2), test(3)]"), @"[1, 1, 2]"); +} + +#[test] +fn test_checkmatch_when_splat_array() { + eval(r#" + def test + case + when *[1, 2] + 1 + else + 2 + end + end + "#); + assert_contains_opcode("test", YARVINSN_checkmatch); + assert_snapshot!(assert_compiles("[test, test]"), @"[1, 1]"); +} + +#[test] +fn test_checkmatch_rescue() { + // Rescue behavior is tested functionally here. It still side-exits because + // JIT exception handling is not supported yet. + eval(r#" + def test + begin + raise TypeError + rescue TypeError + 1 + end + end + "#); + assert_snapshot!(assert_compiles("[test, test]"), @"[1, 1]"); +} + +#[test] +fn test_checkmatch_rescue_splat_array() { + eval(r#" + def test + begin + raise TypeError + rescue *[TypeError, ArgumentError] + 1 + end + end + "#); + assert_snapshot!(assert_compiles("[test, test]"), @"[1, 1]"); +} + +#[test] +fn test_stack_overflow() { + assert_snapshot!(inspect(" + def recurse(n) + return if n == 0 + recurse(n-1) + nil + end + recurse(2) + recurse(2) + begin + recurse(20_000) + rescue SystemStackError + end + "), @"nil"); +} + +#[test] +fn test_invokeblock() { + eval(" + def test + yield + end + test { 41 } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(assert_compiles("test { 42 }"), @"42"); +} + +#[test] +fn test_invokeblock_with_args() { + eval(" + def test(x, y) + yield x, y + end + test(1, 2) { |a, b| a + b } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(assert_compiles("test(1, 2) { |a, b| a + b }"), @"3"); +} + +#[test] +fn test_invokeblock_no_block_given() { + eval(" + def test + yield rescue :error + end + test { } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(assert_compiles("test"), @":error"); +} + +#[test] +fn test_invokeblock_multiple_yields() { + eval(" + def test + yield 1 + yield 2 + yield 3 + end + test { |x| x } + "); + assert_contains_opcode("test", YARVINSN_invokeblock); + assert_snapshot!(assert_compiles(" + results = [] + test { |x| results << x } + results + "), @"[1, 2, 3]"); +} + +#[test] +fn test_invokeblock_ifunc_map() { + eval(" + class MyList + include Enumerable + def each + yield 1 + yield 2 + yield 3 + end + end + def test = MyList.new.map { |x| x * 2 } + test + "); + assert_snapshot!(assert_compiles("test"), @"[2, 4, 6]"); +} + +#[test] +fn test_ccall_variadic_with_multiple_args() { + eval(" + def test + a = [] + a.push(1, 2, 3) + a + end + test + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("test"), @"[1, 2, 3]"); +} + +#[test] +fn test_ccall_variadic_with_no_args() { + eval(" + def test + a = [1] + a.push + end + test + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("test"), @"[1]"); +} + +#[test] +fn test_ccall_variadic_with_no_args_causing_argument_error() { + eval(" + def test + format + rescue ArgumentError + :error + end + test + "); + assert_contains_opcode("test", YARVINSN_opt_send_without_block); + assert_snapshot!(assert_compiles("test"), @":error"); +} + +#[test] +fn test_allocating_in_hir_c_method_is() { + eval(" + def a(f) = test(f) + def test(f) = (f.new if f) + def second = third + def third = nil + a(nil) + a(nil) + class Foo + def self.new = :k + end + second + "); + assert_contains_opcode("test", YARVINSN_opt_new); + assert_snapshot!(assert_compiles_allowing_exits("a(Foo)"), @":k"); +} + +#[test] +fn test_singleton_class_invalidation_annotated_ccall() { + assert_snapshot!(inspect(" + def define_singleton(obj, define) + if define + [nil].reverse_each do + class << obj + def ==(_) + true + end + end + end + end + false + end + def test(define) + obj = BasicObject.new + obj == define_singleton(obj, define) + end + result = [] + result << test(false) + result << test(true) + result + "), @"[false, true]"); +} + +#[test] +fn test_singleton_class_invalidation_optimized_variadic_ccall() { + assert_snapshot!(inspect(" + def define_singleton(arr, define) + if define + [nil].reverse_each do + class << arr + def push(x) + super(x * 1000) + end + end + end + end + 1 + end + def test(define) + arr = [] + val = define_singleton(arr, define) + arr.push(val) + arr[0] + end + result = [] + result << test(false) + result << test(true) + result + "), @"[1, 1000]"); +} + +#[test] +fn test_is_a_string_special_case() { + assert_snapshot!(inspect(r#" + def test(x) + x.is_a?(String) + end + test("foo") + [test("bar"), test(1), test(false), test(:foo), test([]), test({})] + "#), @"[true, false, false, false, false, false]"); +} + +#[test] +fn test_is_a_array_special_case() { + assert_snapshot!(inspect(" + def test(x) + x.is_a?(Array) + end + test([]) + [test([1,2,3]), test([]), test(1), test(false), test(:foo), test('foo'), test({})] + "), @"[true, true, false, false, false, false, false]"); +} + +#[test] +fn test_is_a_hash_special_case() { + assert_snapshot!(inspect(" + def test(x) + x.is_a?(Hash) + end + test({}) + [test({:a => 'b'}), test({}), test(1), test(false), test(:foo), test([]), test('foo')] + "), @"[true, true, false, false, false, false, false]"); +} + +#[test] +fn test_is_a_hash_subclass() { + assert_snapshot!(inspect(" + class MyHash < Hash + end + def test(x) + x.is_a?(Hash) + end + test({}) + test(MyHash.new) + "), @"true"); +} + +#[test] +fn test_is_a_normal_case() { + assert_snapshot!(inspect(r#" + class MyClass + end + def test(x) + x.is_a?(MyClass) + end + test("a") + [test(MyClass.new), test("a")] + "#), @"[true, false]"); +} + +#[test] +fn test_fixnum_div_zero() { + eval(" + def test(n) + n / 0 + rescue ZeroDivisionError => e + e.message + end + test(0) + "); + assert_contains_opcode("test", YARVINSN_opt_div); + assert_snapshot!(assert_compiles_allowing_exits(r#"test(0)"#), @r#""divided by 0""#); +} + +#[test] +fn test_invokesuper_with_local_written_by_blockiseq() { + assert_snapshot!(inspect(r#" + class A + def foo = "A" + end + class B < A + def foo + x = nil + [nil].each do |_| + x = super + end + x + end + end + def test = B.new.foo + test + test + "#), @r#""A""#); +} + +#[test] +fn test_max_iseq_versions() { + let max_versions = max_iseq_versions(); + eval(&format!(" + TEST = -1 + def test = TEST + + # compile and invalidate MAX+1 times + i = 0 + while i < {max_versions} + 1 + test; test # compile a version + + Object.send(:remove_const, :TEST) + TEST = i + + i += 1 + end + ")); + + // It should not exceed MAX_ISEQ_VERSIONS + let iseq = get_method_iseq("self", "test"); + let payload = get_or_create_iseq_payload(iseq); + assert_eq!(payload.versions.len(), max_iseq_versions()); + + // The last call should not discard the JIT code + assert!(matches!(unsafe { payload.versions.last().unwrap().as_ref() }.status, IseqStatus::Compiled(_))); +} + +#[test] +fn test_optional_arguments_side_exit() { + assert_snapshot!(inspect(" + def test(a = (def foo = nil)) = a + test + [test, (undef :foo), test(1)] + "), @"[:foo, nil, 1]"); +} + +#[test] +fn test_call_a_forwardable_method() { + assert_snapshot!(inspect(" + def test_root = forwardable + def forwardable(...) = Array.[](...) + test_root + test_root + "), @"[]"); +} + +#[test] +fn test_send_on_heap_object_in_spilled_arg() { + assert_snapshot!(inspect(" + def entry(a1, a2, a3, a4, a5, a6, a7, a8, a9) + a9.itself.class + end + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) + entry(1, 2, 3, 4, 5, 6, 7, 8, {}) + "), @"Hash"); +} + +#[test] +fn test_send_splat() { + assert_snapshot!(inspect(" + def test(a, b) = [a, b] + def entry(arr) = test(*arr) + entry([1, 2]) + entry([1, 2]) + "), @"[1, 2]"); +} + +#[test] +fn test_send_kwarg() { + assert_snapshot!(inspect(" + def test(a:, b:) = [a, b] + def entry = test(b: 2, a: 1) + entry + entry + "), @"[1, 2]"); +} + +#[test] +fn test_spilled_method_args() { + assert_snapshot!(inspect(" + def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10) + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + def test + foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + end + test + test + "), @"55"); +} + +#[test] +fn test_spilled_method_args_first_and_last() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8,n9) = n1+n9 + a(2,0,0,0,0,0,0,0,-1) + a(2,0,0,0,0,0,0,0,-1) + "), @"1"); +} + +#[test] +fn test_spilled_method_args_last() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8) = n8 + a(1,1,1,1,1,1,1,0) + a(1,1,1,1,1,1,1,0) + "), @"0"); +} + +#[test] +fn test_spilled_method_args_self() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8) = self + a(1,0,0,0,0,0,0,0).to_s + a(1,0,0,0,0,0,0,0).to_s + "), @r#""main""#); +} + +#[test] +fn test_spilled_param_new_array() { + assert_snapshot!(inspect(" + def a(n1,n2,n3,n4,n5,n6,n7,n8) = [n8] + a(0,0,0,0,0,0,0, :ok) + a(0,0,0,0,0,0,0, :ok) + "), @"[:ok]"); +} + +#[test] +fn test_forty_param_method() { + assert_snapshot!(inspect(" + def foo(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,n40) = n40 + foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1) + foo(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1) + "), @"1"); +} + +#[test] +fn test_toplevel_local_after_eval() { + assert_snapshot!(inspect(" + a = 1 + b = 2 + eval('b = 3') + c = 4 + [a, b, c] + "), @"[1, 3, 4]"); +} + +#[test] +fn test_send_exit_with_uninitialized_locals() { + assert_snapshot!(inspect(" + def entry(init) + function_stub_exit(init) + end + + def function_stub_exit(init) + uninitialized_local = 1 if init + uninitialized_local + end + + entry(true) + entry(false) + "), @"nil"); +} + +#[test] +fn test_invokebuiltin_dir_glob() { + assert_snapshot!(inspect(r#" + def test = Dir.glob(".") + test + test + "#), @r#"["."]"#); +} + +#[test] +fn test_profiled_fact() { + assert_snapshot!(inspect(" + def fact(n) + if n == 0 + return 1 + end + return n * fact(n-1) + end + fact(1) + [fact(0), fact(3), fact(6)] + "), @"[1, 6, 720]"); +} + +#[test] +fn test_profiled_fib() { + assert_snapshot!(inspect(" + def fib(n) + if n < 2 + return n + end + return fib(n-1) + fib(n-2) + end + fib(3) + [fib(0), fib(3), fib(4)] + "), @"[0, 2, 3]"); +} + +#[test] +fn test_single_ractor_mode_invalidation() { + assert_snapshot!(inspect(r#" + C = Object.new + + def test + C + rescue Ractor::IsolationError + "errored but not crashed" + end + + test + test + + Ractor.new { + test + }.value + "#), @r#""errored but not crashed""#); +} + +#[test] +fn test_ivar_attr_reader_optimization_with_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + class << self + attr_accessor :bar + + def get_bar + bar + rescue Ractor::IsolationError + 42 + end + end + end + + Foo.bar = [] + + def test + Foo.get_bar + end + + test + test + + Ractor.new { test }.value + "), @"42"); +} + +#[test] +fn test_ivar_get_with_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + def self.set_bar + @bar = [] + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + "), @"42"); +} + +#[test] +fn test_ivar_get_with_already_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + def self.set_bar + @bar = [] + end + + def self.bar + @bar + rescue Ractor::IsolationError + 42 + end + end + + Foo.set_bar + r = Ractor.new { + Ractor.receive + Foo.bar + } + + Foo.bar + Foo.bar + + r << :go + r.value + "), @"42"); +} + +#[test] +fn test_ivar_set_with_multi_ractor_mode() { + assert_snapshot!(inspect(" + class Foo + def self.bar + _foo = 1 + _bar = 2 + begin + @bar = _foo + _bar + rescue Ractor::IsolationError + 42 + end + end + end + + def test + Foo.bar + end + + test + test + + Ractor.new { test }.value + "), @"42"); +} + +#[test] +fn test_global_tracepoint() { + assert_snapshot!(inspect(" + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |event| + if event.method_id == :foo + called = true + end + } + tp.enable do + foo + end + called + "), @"true"); +} + +#[test] +fn test_local_tracepoint() { + assert_snapshot!(inspect(" + def foo = 1 + + foo + foo + + called = false + + tp = TracePoint.new(:return) { |_| called = true } + tp.enable(target: method(:foo)) do + foo + end + called + "), @"true"); +} + +// Regression test: TracePoint return value for methods with rescue that use `return`. +// ZJIT's send fallback uses rb_vm_opt_send_without_block which calls VM_EXEC, +// setting FLAG_FINISH on the callee frame. This changes how throw TAG_RETURN is +// handled, causing the return value to be nil instead of the actual value. +#[test] +fn test_tracepoint_return_value_with_rescue() { + assert_snapshot!(inspect(" + def f_raise + raise + rescue + return :f_raise_return + end + + ary = [] + TracePoint.new(:return, :b_return){|tp| + ary << [tp.event, tp.method_id, tp.return_value] + }.enable{ + send :f_raise + } + ary.pop # last b_return event is not required + ary + "), @"[[:return, :f_raise, :f_raise_return]]"); +} + +// Regression test: polymorphic getivar must not return nil for too-complex shapes. +// Too-complex shapes use hash tables for ivar storage, and rb_shape_get_iv_index() +// doesn't work for them. The polymorphic path must fall through to GetIvar instead. +#[test] +fn test_polymorphic_getivar_complex_shape() { + // Need threshold >= 3 so both shapes get profiled before compilation + set_call_threshold(3); + assert_snapshot!(inspect(r#" + class C + def initialize(foo) + @foo = foo + end + def foo = @foo + end + + # Create a normal object and a too-complex object of the same class + normal = C.new(:normal) + complex = C.new(:complex) + 1001.times { |i| complex.instance_variable_set(:"@v#{i}", i) } + 1001.times { |i| complex.remove_instance_variable(:"@v#{i}") } + + # Profile with both shapes before compilation triggers at call 3 + normal.foo # call 1: profile normal shape + complex.foo # call 2: profile too-complex shape + + # The too-complex object should still return :complex, not nil + [normal.foo, complex.foo] + "#), @"[:normal, :complex]"); +} + +/// When a method with keyword defaults contains a block that creates a lambda, +/// the lambda causes EP escape, which globally patches NoEPEscape PatchPoints. +/// On subsequent calls the PatchPoint side exit (which uses without_locals()) +/// must not leave stale keyword default values in the frame. We solve this by +/// invalidating the ISEQ version on EP escape so the interpreter takes over. +#[test] +fn test_ep_escape_preserves_keyword_default() { + set_call_threshold(1); + assert_snapshot!(inspect(r#" + def target(dumped, additional_methods: []) + dumped.class + additional_methods.each { |m| ->{ m } } + additional_methods + end + + def forwarder(x, **kwargs) + target(x, **kwargs) + end + + 5.times { forwarder("z") } + forwarder("y", additional_methods: [:to_s]) + target("x") + "#), @"[]"); +} + +#[test] +fn test_send_block_to_accepts_no_block() { + // Methods with &nil should raise ArgumentError when called with a block + assert_snapshot!(inspect(" + def m(a, &nil); a end + + def test + m(1) {} + rescue ArgumentError => e + e.message + end + + test + test + "), @r#""no block accepted""#); +} + +#[test] +fn test_send_block_to_method_not_using_block() { + // Passing a block to a method that doesn't use it should still work correctly. + // ZJIT falls back to the interpreter for this case so that unused block + // warnings are properly emitted. + assert_snapshot!(inspect(" + def m_no_block = 42 + + def test + m_no_block {} + end + + test + test + "), @"42"); +} + +#[test] +fn test_send_block_unused_warning_emitted_from_jit() { + // When ZJIT compiles a send with a block as a dynamic dispatch fallback + // (gen_send -> rb_vm_send), warn_unused_block uses cfp->pc for the dedup + // key. We save cfp->pc before calling rb_vm_send so the key is stable + // and won't spuriously collide with prior entries in the dedup table. + assert_snapshot!(inspect(r#" + $warnings = [] + module Warning + def warn(message, category: nil) + $warnings << message + end + end + + def m_unused_block_warn_test = 42 + + def test + $VERBOSE = true + m_unused_block_warn_test {} + $warnings.any? { |w| w.include?("may be ignored") } + end + + test + test + "#), @"true"); +} + +#[test] +fn test_load_immediates_into_registers_before_masking() { + // See https://github.com/ruby/ruby/pull/16669 -- this is a reduced reproduction from a Ruby + // spec. + set_call_threshold(2); + assert_snapshot!(inspect(r#" + def test + klass = Class.new do + def ===(o) + true + end + end + + case 1 + when klass.new + :called + end == :called + end + + test + test + "#), @"true"); +} + +#[test] +fn test_loop_terminates() { + set_call_threshold(3); + // Previous worklist-based type inference only worked for maximal SSA. This is a regression + // test for hanging. + assert_snapshot!(inspect(r#" + class TheClass + def set_value_loop + i = 0 + while i < 10 + @levar ||= i + i += 1 + end + end + end + + 3.times do |i| + TheClass.new.set_value_loop + end + "#), @"3"); +} + +// Regression test: getlocal with level=0 after setlocal_WC_0 was loading stale EP +// memory, causing Array#pack with buffer: keyword to receive the wrong buffer VALUE. +// See https://github.com/ruby/ruby/pull/16736 +#[test] +fn test_getlocal_level_zero_after_setlocal_wc_0() { + assert_snapshot!(inspect(r#" + def test + b = +"x" + v = 2 + [v].pack("C*", buffer: b) + b.size + end + test + "#), @"2"); +} |
