diff options
-rw-r--r-- | .github/actions/launchable/setup/action.yml | 61 | ||||
-rw-r--r-- | test/ruby/test_zjit.rb | 10 | ||||
-rw-r--r-- | zjit/src/hir.rs | 119 |
3 files changed, 147 insertions, 43 deletions
diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml index 376a7dfed4..1cd28bf2d7 100644 --- a/.github/actions/launchable/setup/action.yml +++ b/.github/actions/launchable/setup/action.yml @@ -166,63 +166,38 @@ runs: btest_test_suite="yjit-${btest_test_suite}" test_spec_test_suite="yjit-${test_spec_test_suite}" fi - launchable record build --name "${build_name}" - if [ "${test_all_enabled}" = "true" ]; then - test_all_session=$(launchable record session \ + # launchable_setup target var -- refers ${target} prefixed variables + launchable_setup() { + local target=$1 session + eval [ "\${${target}_enabled}" = "true" ] || return + eval local suite=\${${target}_test_suite} + session=$(launchable record session \ --build "${build_name}" \ --observation \ --flavor os="${{ inputs.os }}" \ --flavor test_task="${{ inputs.test-task }}" \ --flavor test_opts="${test_opts}" \ --flavor workflow="${{ github.workflow }}" \ - --test-suite ${test_all_test_suite} \ + --test-suite ${suite} \ ) launchable subset \ --get-tests-from-previous-sessions \ --non-blocking \ --target 90% \ - --session "${test_all_session}" \ + --session "${session}" \ raw > /dev/null - echo test_all_session="${test_all_session}" >> $GITHUB_OUTPUT - echo "TESTS=${TESTS} --launchable-test-reports=${test_all_report_file}" >> $GITHUB_ENV + echo "${target}_session=${session}" >> $GITHUB_OUTPUT + } + + launchable record build --name "${build_name}" + if launchable_setup test_all; then + echo "TESTS=${TESTS:+$TESTS }--launchable-test-reports=${test_all_report_file}" >> $GITHUB_ENV fi - if [ "${btest_enabled}" = "true" ]; then - btest_session=$(launchable record session \ - --build "${build_name}" \ - --observation \ - --flavor os="${{ inputs.os }}" \ - --flavor test_task="${{ inputs.test-task }}" \ - --flavor test_opts="${test_opts}" \ - --flavor workflow="${{ github.workflow }}" \ - --test-suite ${btest_test_suite} \ - ) - launchable subset \ - --get-tests-from-previous-sessions \ - --non-blocking \ - --target 90% \ - --session "${btest_session}" \ - raw > /dev/null - echo btest_session="${btest_session}" >> $GITHUB_OUTPUT - echo "BTESTS=${BTESTS} --launchable-test-reports=${btest_report_file}" >> $GITHUB_ENV + if launchable_setup btest; then + echo "BTESTS=${BTESTS:+$BTESTS }--launchable-test-reports=${btest_report_file}" >> $GITHUB_ENV fi - if [ "${test_spec_enabled}" = "true" ]; then - test_spec_session=$(launchable record session \ - --build "${build_name}" \ - --observation \ - --flavor os="${{ inputs.os }}" \ - --flavor test_task="${{ inputs.test-task }}" \ - --flavor test_opts="${test_opts}" \ - --flavor workflow="${{ github.workflow }}" \ - --test-suite ${test_spec_test_suite} \ - ) - launchable subset \ - --get-tests-from-previous-sessions \ - --non-blocking \ - --target 90% \ - --session "${test_spec_session}" \ - raw > /dev/null - echo test_spec_session="${test_spec_session}" >> $GITHUB_OUTPUT - echo "SPECOPTS=${SPECOPTS} --launchable-test-reports=${test_spec_report_dir}" >> $GITHUB_ENV + if launchable_setup test_spec; then + echo "SPECOPTS=${SPECOPTS:$SPECOPTS }--launchable-test-reports=${test_spec_report_dir}" >> $GITHUB_ENV fi if: steps.enable-launchable.outputs.enable-launchable env: diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index e10e9a8742..6e0f274c30 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -714,6 +714,16 @@ class TestZJIT < Test::Unit::TestCase end end + def test_dupn + assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn] + def test(array) = (array[1, 2] ||= :rhs) + + one = [1, 1] + start_empty = [] + [test(one), one, test(start_empty), start_empty] + RUBY + end + def test_send_backtrace backtrace = [ "-e:2:in 'Object#jit_frame1'", diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index fca8c237fd..2bd7174f2d 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -496,6 +496,9 @@ pub enum Insn { FixnumGt { left: InsnId, right: InsnId }, FixnumGe { left: InsnId, right: InsnId }, + // Distinct from `SendWithoutBlock` with `mid:to_s` because does not have a patch point for String to_s being redefined + ObjToString { val: InsnId, call_info: CallInfo, cd: *const rb_call_data, state: InsnId }, + /// Side-exit if val doesn't have the expected type. GuardType { val: InsnId, guard_type: Type, state: InsnId }, /// Side-exit if val is not the expected VALUE. @@ -695,6 +698,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ToNewArray { val, .. } => write!(f, "ToNewArray {val}"), Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"), Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"), + Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") }, Insn::SideExit { .. } => write!(f, "SideExit"), Insn::PutSpecialObject { value_type } => { write!(f, "PutSpecialObject {}", value_type) @@ -1013,6 +1017,12 @@ impl Function { FixnumLt { left, right } => FixnumLt { left: find!(*left), right: find!(*right) }, FixnumLe { left, right } => FixnumLe { left: find!(*left), right: find!(*right) }, PutSpecialObject { value_type } => PutSpecialObject { value_type: *value_type }, + ObjToString { val, call_info, cd, state } => ObjToString { + val: find!(*val), + call_info: call_info.clone(), + cd: *cd, + state: *state, + }, SendWithoutBlock { self_val, call_info, cd, args, state } => SendWithoutBlock { self_val: find!(*self_val), call_info: call_info.clone(), @@ -1143,6 +1153,7 @@ impl Function { Insn::GetIvar { .. } => types::BasicObject, Insn::ToNewArray { .. } => types::ArrayExact, Insn::ToArray { .. } => types::ArrayExact, + Insn::ObjToString { .. } => types::BasicObject, } } @@ -1386,6 +1397,15 @@ impl Function { let replacement = self.push_insn(block, Insn::Const { val: Const::Value(unsafe { (*ice).value }) }); self.make_equal_to(insn_id, replacement); } + Insn::ObjToString { val, call_info, cd, state, .. } => { + if self.is_a(val, types::StringExact) { + // behaves differently from `SendWithoutBlock` with `mid:to_s` because ObjToString should not have a patch point for String to_s being redefined + self.make_equal_to(insn_id, val); + } else { + let replacement = self.push_insn(block, Insn::SendWithoutBlock { self_val: val, call_info, cd, args: vec![], state }); + self.make_equal_to(insn_id, replacement) + } + } _ => { self.push_insn_id(block, insn_id); } } } @@ -1758,6 +1778,10 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } + Insn::ObjToString { val, state, .. } => { + worklist.push_back(val); + worklist.push_back(state); + } Insn::GetGlobal { state, .. } | Insn::SideExit { state } => worklist.push_back(state), } @@ -2405,6 +2429,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { } YARVINSN_pop => { state.stack_pop()?; } YARVINSN_dup => { state.stack_push(state.stack_top()?); } + YARVINSN_dupn => { + // Duplicate the top N element of the stack. As we push, n-1 naturally + // points higher in the original stack. + let n = get_arg(pc, 0).as_usize(); + for _ in 0..n { + state.stack_push(state.stack_topn(n-1)?); + } + } YARVINSN_swap => { let right = state.stack_pop()?; let left = state.stack_pop()?; @@ -2669,6 +2701,26 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { let insn_id = fun.push_insn(block, Insn::InvokeBuiltin { bf, args, state: exit_id }); state.stack_push(insn_id); } + YARVINSN_objtostring => { + let cd: *const rb_call_data = get_arg(pc, 0).as_ptr(); + let call_info = unsafe { rb_get_call_data_ci(cd) }; + + if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { + assert!(false, "objtostring should not have unknown call type"); + } + let argc = unsafe { vm_ci_argc((*cd).ci) }; + assert_eq!(0, argc, "objtostring should not have args"); + + let method_name: String = unsafe { + let mid = rb_vm_ci_mid(call_info); + mid.contents_lossy().into_owned() + }; + + let recv = state.stack_pop()?; + let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); + let objtostring = fun.push_insn(block, Insn::ObjToString { val: recv, call_info: CallInfo { method_name }, cd, state: exit_id }); + state.stack_push(objtostring) + } _ => { // Unknown opcode; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); @@ -4314,6 +4366,43 @@ mod tests { Return v8 "#]]); } + + #[test] + fn dupn() { + eval(" + def test(x) = (x[0, 1] ||= 2) + "); + assert_method_hir_with_opcode("test", YARVINSN_dupn, expect![[r#" + fn test: + bb0(v0:BasicObject, v1:BasicObject): + v3:NilClassExact = Const Value(nil) + v4:Fixnum[0] = Const Value(0) + v5:Fixnum[1] = Const Value(1) + v7:BasicObject = SendWithoutBlock v1, :[], v4, v5 + v8:CBool = Test v7 + IfTrue v8, bb1(v0, v1, v3, v1, v4, v5, v7) + v10:Fixnum[2] = Const Value(2) + v12:BasicObject = SendWithoutBlock v1, :[]=, v4, v5, v10 + Return v10 + bb1(v14:BasicObject, v15:BasicObject, v16:NilClassExact, v17:BasicObject, v18:Fixnum[0], v19:Fixnum[1], v20:BasicObject): + Return v20 + "#]]); + } + + #[test] + fn test_objtostring() { + eval(" + def test = \"#{1}\" + "); + assert_method_hir_with_opcode("test", YARVINSN_objtostring, expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v3:Fixnum[1] = Const Value(1) + v5:BasicObject = ObjToString v3 + SideExit + "#]]); + } } #[cfg(test)] @@ -5872,4 +5961,34 @@ mod opt_tests { Return v7 "#]]); } + + #[test] + fn test_objtostring_string() { + eval(r##" + def test = "#{('foo')}" + "##); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v4:StringExact = StringCopy v3 + SideExit + "#]]); + } + + #[test] + fn test_objtostring_with_non_string() { + eval(r##" + def test = "#{1}" + "##); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v3:Fixnum[1] = Const Value(1) + v8:BasicObject = SendWithoutBlock v3, :to_s + SideExit + "#]]); + } } |