From 070b7d40d35f8dbcf81809c6b19be2bbba1c8bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Co=C3=AAlho?= <36938811+tomascco@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:03:22 -0300 Subject: [PATCH 01/35] ZJIT: constant fold bitwise AND (&) operations (#16226) Following up https://github.com/ruby/ruby/pull/16225, I'm implementing fold on AND (&) operations. - Ruby turns this operation into a C `&` in https://github.com/tomascco/ruby/blob/880eb3c76f09c44c85389ac5efbc555afafa3e6f/numeric.c#L5197-L5210 - Rustc compiler emits an `and` assembly instruction on x86: https://godbolt.org/z/Ee74vKTfb So the operations are equivalent. I tested manually some cases and the results match. --- zjit/src/hir.rs | 6 ++++ zjit/src/hir/opt_tests.rs | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index cc47c447400cae..a3dd9697f67515 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4584,6 +4584,12 @@ impl Function { _ => None, }) } + Insn::FixnumAnd { left, right, .. } => { + self.fold_fixnum_bop(insn_id, left, right, |l, r| match (l, r) { + (Some(l), Some(r)) => Some(l & r), + _ => None, + }) + } Insn::FixnumEq { left, right, .. } => { self.fold_fixnum_pred(insn_id, left, right, |l, r| match (l, r) { (Some(l), Some(r)) => Some(l == r), diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 5d3f8513278833..8268245e58a4c0 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -508,6 +508,66 @@ mod hir_opt_tests { "); } + #[test] + fn test_fold_fixnum_and() { + eval(" + def test + 4 & -7 + end + "); + + assert_snapshot!(inspect("test"), @"0"); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[4] = Const Value(4) + v12:Fixnum[-7] = Const Value(-7) + PatchPoint MethodRedefined(Integer@0x1000, &@0x1008, cme:0x1010) + v25:Fixnum[0] = Const Value(0) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + + #[test] + fn test_fold_fixnum_and_with_negative_self() { + eval(" + def test + -4 & 7 + end + "); + + assert_snapshot!(inspect("test"), @"4"); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v10:Fixnum[-4] = Const Value(-4) + v12:Fixnum[7] = Const Value(7) + PatchPoint MethodRedefined(Integer@0x1000, &@0x1008, cme:0x1010) + v25:Fixnum[4] = Const Value(4) + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v25 + "); + } + #[test] fn test_fold_fixnum_less() { eval(" From f3ee4bd0abe2f72bf804a9f564ab763fd5a59d48 Mon Sep 17 00:00:00 2001 From: Nozomi Hijikata <121233810+nozomemein@users.noreply.github.com> Date: Tue, 24 Feb 2026 08:58:04 +0900 Subject: [PATCH 02/35] ZJIT: Remove redundant snapshot for opt_getconstant_path (#16229) --- zjit/src/hir.rs | 4 +- zjit/src/hir/opt_tests.rs | 428 +++++++++++++++++++------------------- zjit/src/hir/tests.rs | 24 +-- 3 files changed, 227 insertions(+), 229 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a3dd9697f67515..7d98dce666b874 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6714,9 +6714,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } YARVINSN_opt_getconstant_path => { let ic = get_arg(pc, 0).as_ptr(); - // TODO: Remove this extra Snapshot and pass `exit_id` to `GetConstantPath` instead. - let snapshot = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - state.stack_push(fun.push_insn(block, Insn::GetConstantPath { ic, state: snapshot })); + state.stack_push(fun.push_insn(block, Insn::GetConstantPath { ic, state: exit_id })); } YARVINSN_branchunless | YARVINSN_branchunless_without_ints => { if opcode == YARVINSN_branchunless { diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 8268245e58a4c0..7b831b1484f1f3 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2830,10 +2830,10 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - v15:Fixnum[5] = Const Value(5) + v10:BasicObject = GetConstantPath 0x1000 + v14:Fixnum[5] = Const Value(5) CheckInterrupts - Return v15 + Return v14 "); } @@ -2976,15 +2976,15 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:NilClass): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, M) - v30:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(Module@0x1010) PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) IncrCounter inline_cfunc_optimized_send_count - v35:StringExact|NilClass = CCall v30, :Module#name@0x1048 + v34:StringExact|NilClass = CCall v29, :Module#name@0x1048 PatchPoint NoEPEscape(test) - v22:Fixnum[1] = Const Value(1) + v21:Fixnum[1] = Const Value(1) CheckInterrupts - Return v22 + Return v21 "); } @@ -3037,9 +3037,9 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v19:Class[C@0x1008] = Const Value(VALUE(0x1008)) + v18:Class[C@0x1008] = Const Value(VALUE(0x1008)) CheckInterrupts - Return v19 + Return v18 "); } @@ -3062,19 +3062,19 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v30:Class[String@0x1008] = Const Value(VALUE(0x1008)) + v26:Class[String@0x1008] = Const Value(VALUE(0x1008)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1010, Class) - v33:Class[Class@0x1018] = Const Value(VALUE(0x1018)) + v29:Class[Class@0x1018] = Const Value(VALUE(0x1018)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1020, Module) - v36:Class[Module@0x1028] = Const Value(VALUE(0x1028)) + v32:Class[Module@0x1028] = Const Value(VALUE(0x1028)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1030, BasicObject) - v39:Class[BasicObject@0x1038] = Const Value(VALUE(0x1038)) - v22:ArrayExact = NewArray v30, v33, v36, v39 + v35:Class[BasicObject@0x1038] = Const Value(VALUE(0x1038)) + v18:ArrayExact = NewArray v26, v29, v32, v35 CheckInterrupts - Return v22 + Return v18 "); } @@ -3097,13 +3097,13 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Enumerable) - v24:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v22:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1010, Kernel) - v27:ModuleExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) - v16:ArrayExact = NewArray v24, v27 + v25:ModuleExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + v14:ArrayExact = NewArray v22, v25 CheckInterrupts - Return v16 + Return v14 "); } @@ -3128,9 +3128,9 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, MY_MODULE) - v19:ModuleSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:ModuleSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) CheckInterrupts - Return v19 + Return v18 "); } @@ -3357,14 +3357,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, M) - v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(Module@0x1010) PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) IncrCounter inline_iseq_optimized_send_count - v27:Class[Module@0x1010] = Const Value(VALUE(0x1010)) + v26:Class[Module@0x1010] = Const Value(VALUE(0x1010)) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v27 + Return v26 "); } @@ -3992,9 +3992,9 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 + v10:BasicObject = GetConstantPath 0x1000 CheckInterrupts - Return v11 + Return v10 "); } @@ -4016,9 +4016,9 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 + v10:BasicObject = GetConstantPath 0x1000 CheckInterrupts - Return v11 + Return v10 "); } @@ -4041,9 +4041,9 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Kernel) - v19:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) CheckInterrupts - Return v19 + Return v18 "); } @@ -4072,9 +4072,9 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Foo::Bar::C) - v19:Class[Foo::Bar::C@0x1008] = Const Value(VALUE(0x1008)) + v18:Class[Foo::Bar::C@0x1008] = Const Value(VALUE(0x1008)) CheckInterrupts - Return v19 + Return v18 "); } @@ -4098,17 +4098,17 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v44:Class[C@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) + v43:Class[C@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) - v47:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + v46:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) - v51:NilClass = Const Value(nil) + v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts CheckInterrupts - Return v47 + Return v46 "); } @@ -4136,17 +4136,17 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v47:Class[C@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) - v16:Fixnum[1] = Const Value(1) + v46:Class[C@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v15:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) - v50:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) + v49:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) - v53:BasicObject = SendDirect v50, 0x1068, :initialize (0x1078), v16 + v52:BasicObject = SendDirect v49, 0x1068, :initialize (0x1078), v15 CheckInterrupts CheckInterrupts - Return v50 + Return v49 "); } @@ -4169,17 +4169,17 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Object) - v44:Class[Object@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) + v43:Class[Object@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Object@0x1008, new@0x1009, cme:0x1010) - v47:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) + v46:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) PatchPoint NoSingletonClass(Object@0x1008) PatchPoint MethodRedefined(Object@0x1008, initialize@0x1038, cme:0x1040) - v51:NilClass = Const Value(nil) + v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts CheckInterrupts - Return v47 + Return v46 "); } @@ -4202,17 +4202,17 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, BasicObject) - v44:Class[BasicObject@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) + v43:Class[BasicObject@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1009, cme:0x1010) - v47:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) + v46:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) PatchPoint NoSingletonClass(BasicObject@0x1008) PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1038, cme:0x1040) - v51:NilClass = Const Value(nil) + v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts CheckInterrupts - Return v47 + Return v46 "); } @@ -4235,15 +4235,15 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Hash) - v44:Class[Hash@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) + v43:Class[Hash@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Hash@0x1008, new@0x1009, cme:0x1010) - v47:HashExact = ObjectAllocClass Hash:VALUE(0x1008) + v46:HashExact = ObjectAllocClass Hash:VALUE(0x1008) IncrCounter complex_arg_pass_param_block - v20:BasicObject = Send v47, :initialize # SendFallbackReason: Complex argument passing + v19:BasicObject = Send v46, :initialize # SendFallbackReason: Complex argument passing CheckInterrupts CheckInterrupts - Return v47 + Return v46 "); assert_snapshot!(inspect("test"), @"{}"); } @@ -4267,15 +4267,15 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Array) - v47:Class[Array@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) - v16:Fixnum[1] = Const Value(1) + v46:Class[Array@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v15:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Array@0x1008, new@0x1009, cme:0x1010) PatchPoint NoSingletonClass(Class@0x1038) PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) - v58:BasicObject = CCallVariadic v47, :Array.new@0x1040, v16 + v57:BasicObject = CCallVariadic v46, :Array.new@0x1040, v15 CheckInterrupts - Return v58 + Return v57 "); } @@ -4298,17 +4298,17 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Set) - v44:Class[Set@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) + v43:Class[Set@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Set@0x1008, new@0x1009, cme:0x1010) - v18:HeapBasicObject = ObjectAlloc v44 + v17:HeapBasicObject = ObjectAlloc v43 PatchPoint NoSingletonClass(Set@0x1008) PatchPoint MethodRedefined(Set@0x1008, initialize@0x1038, cme:0x1040) - v50:SetExact = GuardType v18, SetExact - v51:BasicObject = CCallVariadic v50, :Set#initialize@0x1068 + v49:SetExact = GuardType v17, SetExact + v50:BasicObject = CCallVariadic v49, :Set#initialize@0x1068 CheckInterrupts CheckInterrupts - Return v18 + Return v17 "); } @@ -4331,14 +4331,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v44:Class[String@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) + v43:Class[String@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(String@0x1008, new@0x1009, cme:0x1010) PatchPoint NoSingletonClass(Class@0x1038) PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) - v55:BasicObject = CCallVariadic v44, :String.new@0x1040 + v54:BasicObject = CCallVariadic v43, :String.new@0x1040 CheckInterrupts - Return v55 + Return v54 "); } @@ -4361,18 +4361,18 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Regexp) - v48:Class[Regexp@0x1008] = Const Value(VALUE(0x1008)) - v13:NilClass = Const Value(nil) - v16:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) - v17:StringExact = StringCopy v16 + v47:Class[Regexp@0x1008] = Const Value(VALUE(0x1008)) + v12:NilClass = Const Value(nil) + v15:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v16:StringExact = StringCopy v15 PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020) - v51:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) + v50:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) PatchPoint NoSingletonClass(Regexp@0x1008) PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) - v55:BasicObject = CCallVariadic v51, :Regexp#initialize@0x1078, v17 + v54:BasicObject = CCallVariadic v50, :Regexp#initialize@0x1078, v16 CheckInterrupts CheckInterrupts - Return v51 + Return v50 "); } @@ -5784,19 +5784,19 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, S) - v24:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v13:Fixnum[0] = Const Value(0) + v23:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:Fixnum[0] = Const Value(0) PatchPoint NoSingletonClass(Array@0x1010) PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) - v28:CInt64[0] = UnboxFixnum v13 - v29:CInt64 = ArrayLength v24 - v30:CInt64[0] = GuardLess v28, v29 - v31:CInt64[0] = Const CInt64(0) - v32:CInt64[0] = GuardGreaterEq v30, v31 - v33:BasicObject = ArrayAref v24, v32 + v27:CInt64[0] = UnboxFixnum v12 + v28:CInt64 = ArrayLength v23 + v29:CInt64[0] = GuardLess v27, v28 + v30:CInt64[0] = Const CInt64(0) + v31:CInt64[0] = GuardGreaterEq v29, v30 + v32:BasicObject = ArrayAref v23, v31 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v33 + Return v32 "); // TODO(max): Check the result of `S[0] = 5; test` using `inspect` to make sure that we // actually do the load at run-time. @@ -6048,9 +6048,9 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, MY_SET) - v19:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v18:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) CheckInterrupts - Return v19 + Return v18 "); } @@ -6185,13 +6185,13 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Foo) - v23:Class[Foo@0x1008] = Const Value(VALUE(0x1008)) - v13:Fixnum[100] = Const Value(100) + v22:Class[Foo@0x1008] = Const Value(VALUE(0x1008)) + v12:Fixnum[100] = Const Value(100) PatchPoint NoSingletonClass(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) IncrCounter inline_iseq_optimized_send_count CheckInterrupts - Return v13 + Return v12 "); } @@ -7221,17 +7221,17 @@ mod hir_opt_tests { v13:ArrayExact = NewArray PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, A) - v37:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v35:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1010, B) - v40:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + v38:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) PatchPoint NoSingletonClass(Array@0x1020) PatchPoint MethodRedefined(Array@0x1020, zip@0x1028, cme:0x1030) - v44:BasicObject = CCallVariadic v37, :zip@0x1058, v40 - v24:BasicObject = GetLocal :result, l0, EP@3 + v42:BasicObject = CCallVariadic v35, :zip@0x1058, v38 + v22:BasicObject = GetLocal :result, l0, EP@3 PatchPoint NoEPEscape(test) CheckInterrupts - Return v24 + Return v22 "); } @@ -7392,14 +7392,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, O) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - v26:CShape = LoadField v21, :_shape_id@0x1048 - v27:CShape[0x1049] = GuardBitEquals v26, CShape(0x1049) - v28:NilClass = Const Value(nil) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v28 + Return v27 "); } @@ -7428,14 +7428,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, O) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - v26:CShape = LoadField v21, :_shape_id@0x1048 - v27:CShape[0x1049] = GuardBitEquals v26, CShape(0x1049) - v28:NilClass = Const Value(nil) + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v27:NilClass = Const Value(nil) CheckInterrupts - Return v28 + Return v27 "); } @@ -8231,14 +8231,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, H) - v24:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v13:StaticSymbol[:a] = Const Value(VALUE(0x1010)) + v23:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:StaticSymbol[:a] = Const Value(VALUE(0x1010)) PatchPoint NoSingletonClass(Hash@0x1018) PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) - v28:BasicObject = HashAref v24, v13 + v27:BasicObject = HashAref v23, v12 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v28 + Return v27 "); } @@ -8365,15 +8365,15 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Thread) - v21:Class[Thread@0x1008] = Const Value(VALUE(0x1008)) + v20:Class[Thread@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) - v25:CPtr = LoadEC - v26:CPtr = LoadField v25, :thread_ptr@0x1048 - v27:BasicObject = LoadField v26, :self@0x1049 + v24:CPtr = LoadEC + v25:CPtr = LoadField v24, :thread_ptr@0x1048 + v26:BasicObject = LoadField v25, :self@0x1049 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v27 + Return v26 "); } @@ -11114,14 +11114,14 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v27:Class[String@0x1008] = Const Value(VALUE(0x1008)) + v26:Class[String@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) PatchPoint NoSingletonClass(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) - v31:BoolExact = IsA v9, v27 + v30:BoolExact = IsA v9, v26 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v31 + Return v30 "); } @@ -11146,14 +11146,14 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Kernel) - v27:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v26:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) PatchPoint NoSingletonClass(Module@0x1010) PatchPoint MethodRedefined(Module@0x1010, ===@0x1018, cme:0x1020) IncrCounter inline_cfunc_optimized_send_count - v32:BoolExact = CCall v27, :Module#===@0x1048, v9 + v31:BoolExact = CCall v26, :Module#===@0x1048, v9 CheckInterrupts - Return v32 + Return v31 "); } @@ -11178,14 +11178,14 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v25:Class[String@0x1008] = Const Value(VALUE(0x1008)) + v24:Class[String@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1008) PatchPoint MethodRedefined(String@0x1008, is_a?@0x1009, cme:0x1010) - v29:StringExact = GuardType v9, StringExact - v30:BoolExact = IsA v29, v25 + v28:StringExact = GuardType v9, StringExact + v29:BoolExact = IsA v28, v24 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v30 + Return v29 "); } @@ -11210,13 +11210,13 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Kernel) - v25:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v24:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1010) PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) - v29:StringExact = GuardType v9, StringExact - v30:BasicObject = CCallWithFrame v29, :Kernel#is_a?@0x1048, v25 + v28:StringExact = GuardType v9, StringExact + v29:BasicObject = CCallWithFrame v28, :Kernel#is_a?@0x1048, v24 CheckInterrupts - Return v30 + Return v29 "); } @@ -11244,14 +11244,14 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Integer) - v29:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) + v28:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1010) PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) - v33:StringExact = GuardType v9, StringExact + v32:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count - v21:Fixnum[5] = Const Value(5) + v20:Fixnum[5] = Const Value(5) CheckInterrupts - Return v21 + Return v20 "); } @@ -11279,14 +11279,14 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Integer) - v31:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) + v30:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) PatchPoint NoSingletonClass(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) IncrCounter inline_cfunc_optimized_send_count - v23:Fixnum[5] = Const Value(5) + v22:Fixnum[5] = Const Value(5) CheckInterrupts - Return v23 + Return v22 "); } @@ -11311,14 +11311,14 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) - v25:Class[String@0x1008] = Const Value(VALUE(0x1008)) + v24:Class[String@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1008) PatchPoint MethodRedefined(String@0x1008, kind_of?@0x1009, cme:0x1010) - v29:StringExact = GuardType v9, StringExact - v30:BoolExact = IsA v29, v25 + v28:StringExact = GuardType v9, StringExact + v29:BoolExact = IsA v28, v24 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v30 + Return v29 "); } @@ -11343,13 +11343,13 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Kernel) - v25:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v24:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1010) PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1018, cme:0x1020) - v29:StringExact = GuardType v9, StringExact - v30:BasicObject = CCallWithFrame v29, :Kernel#kind_of?@0x1048, v25 + v28:StringExact = GuardType v9, StringExact + v29:BasicObject = CCallWithFrame v28, :Kernel#kind_of?@0x1048, v24 CheckInterrupts - Return v30 + Return v29 "); } @@ -11377,14 +11377,14 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Integer) - v29:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) + v28:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1010) PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1018, cme:0x1020) - v33:StringExact = GuardType v9, StringExact + v32:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count - v21:Fixnum[5] = Const Value(5) + v20:Fixnum[5] = Const Value(5) CheckInterrupts - Return v21 + Return v20 "); } @@ -11737,12 +11737,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_OBJ) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozen@0x1010) PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020) - v30:Fixnum[1] = Const Value(1) + v29:Fixnum[1] = Const Value(1) CheckInterrupts - Return v30 + Return v29 "); } @@ -11778,12 +11778,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, MULTI_FROZEN) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestMultiIvars@0x1010) PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020) - v30:Fixnum[20] = Const Value(20) + v29:Fixnum[20] = Const Value(20) CheckInterrupts - Return v30 + Return v29 "); } @@ -11817,12 +11817,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_STR) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenStr@0x1010) PatchPoint MethodRedefined(TestFrozenStr@0x1010, name@0x1018, cme:0x1020) - v30:StringExact[VALUE(0x1048)] = Const Value(VALUE(0x1048)) + v29:StringExact[VALUE(0x1048)] = Const Value(VALUE(0x1048)) CheckInterrupts - Return v30 + Return v29 "); } @@ -11856,12 +11856,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_NIL) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenNil@0x1010) PatchPoint MethodRedefined(TestFrozenNil@0x1010, value@0x1018, cme:0x1020) - v30:NilClass = Const Value(nil) + v29:NilClass = Const Value(nil) CheckInterrupts - Return v30 + Return v29 "); } @@ -11895,14 +11895,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, UNFROZEN_OBJ) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestUnfrozen@0x1010) PatchPoint MethodRedefined(TestUnfrozen@0x1010, a@0x1018, cme:0x1020) - v26:CShape = LoadField v21, :_shape_id@0x1048 - v27:CShape[0x1049] = GuardBitEquals v26, CShape(0x1049) - v28:BasicObject = LoadField v21, :@a@0x104a + v25:CShape = LoadField v20, :_shape_id@0x1048 + v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) + v27:BasicObject = LoadField v20, :@a@0x104a CheckInterrupts - Return v28 + Return v27 "); } @@ -11936,12 +11936,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_READER) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestAttrReader@0x1010) PatchPoint MethodRedefined(TestAttrReader@0x1010, value@0x1018, cme:0x1020) - v30:Fixnum[42] = Const Value(42) + v29:Fixnum[42] = Const Value(42) CheckInterrupts - Return v30 + Return v29 "); } @@ -11975,12 +11975,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_SYM) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenSym@0x1010) PatchPoint MethodRedefined(TestFrozenSym@0x1010, sym@0x1018, cme:0x1020) - v30:StaticSymbol[:hello] = Const Value(VALUE(0x1048)) + v29:StaticSymbol[:hello] = Const Value(VALUE(0x1048)) CheckInterrupts - Return v30 + Return v29 "); } @@ -12014,12 +12014,12 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_TRUE) - v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenBool@0x1010) PatchPoint MethodRedefined(TestFrozenBool@0x1010, flag@0x1018, cme:0x1020) - v30:TrueClass = Const Value(true) + v29:TrueClass = Const Value(true) CheckInterrupts - Return v30 + Return v29 "); } @@ -12094,21 +12094,21 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, NESTED_FROZEN) - v29:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v27:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestNestedAccess@0x1010) PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020) - v54:Fixnum[100] = Const Value(100) + v52:Fixnum[100] = Const Value(100) PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1048, NESTED_FROZEN) - v35:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v33:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestNestedAccess@0x1010) PatchPoint MethodRedefined(TestNestedAccess@0x1010, y@0x1050, cme:0x1058) - v56:Fixnum[200] = Const Value(200) + v54:Fixnum[200] = Const Value(200) PatchPoint MethodRedefined(Integer@0x1080, +@0x1088, cme:0x1090) - v57:Fixnum[300] = Const Value(300) + v55:Fixnum[300] = Const Value(300) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v57 + Return v55 "); } @@ -12132,14 +12132,14 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, S) - v21:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(String@0x1010) PatchPoint MethodRedefined(String@0x1010, bytesize@0x1018, cme:0x1020) - v25:CInt64 = LoadField v21, :len@0x1048 - v26:Fixnum = BoxFixnum v25 + v24:CInt64 = LoadField v20, :len@0x1048 + v25:Fixnum = BoxFixnum v24 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - Return v26 + Return v25 "); } @@ -12196,10 +12196,10 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) - v22:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v13:BasicObject = Send v22, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:BasicObject = Send v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL CheckInterrupts - Return v13 + Return v12 "); } @@ -12252,10 +12252,10 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) - v22:BasicObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v13:BasicObject = Send v22, :initialize # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v21:BasicObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:BasicObject = Send v21, :initialize # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL CheckInterrupts - Return v13 + Return v12 "); } @@ -12280,10 +12280,10 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) - v22:ObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v13:BasicObject = Send v22, :toplevel_method # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v21:ObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:BasicObject = Send v21, :toplevel_method # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL CheckInterrupts - Return v13 + Return v12 "); } @@ -12340,10 +12340,10 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Obj) - v22:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v13:BasicObject = Send v22, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL + v21:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v12:BasicObject = Send v21, :secret # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL CheckInterrupts - Return v13 + Return v12 "); } @@ -13164,27 +13164,27 @@ mod hir_opt_tests { v70:Falsy = RefineType v59, Falsy PatchPoint NoSingletonClass(Object@0x1018) PatchPoint MethodRedefined(Object@0x1018, lambda@0x1020, cme:0x1028) - v115:HeapObject[class_exact*:Object@VALUE(0x1018)] = GuardType v57, HeapObject[class_exact*:Object@VALUE(0x1018)] - v116:BasicObject = CCallWithFrame v115, :Kernel#lambda@0x1050, block=0x1058 + v114:HeapObject[class_exact*:Object@VALUE(0x1018)] = GuardType v57, HeapObject[class_exact*:Object@VALUE(0x1018)] + v115:BasicObject = CCallWithFrame v114, :Kernel#lambda@0x1050, block=0x1058 v74:BasicObject = GetLocal :list, l0, EP@6 v76:BasicObject = GetLocal :iter_method, l0, EP@4 v77:BasicObject = GetLocal :kwsplat, l0, EP@3 - SetLocal :sep, l0, EP@5, v116 - Jump bb8(v57, v74, v116, v76, v77) + SetLocal :sep, l0, EP@5, v115 + Jump bb8(v57, v74, v115, v76, v77) bb8(v81:BasicObject, v82:BasicObject, v83:BasicObject, v84:BasicObject, v85:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1060, CONST) - v111:HashExact[VALUE(0x1068)] = Const Value(VALUE(0x1068)) - SetLocal :kwsplat, l0, EP@3, v111 - v95:BasicObject = GetLocal :list, l0, EP@6 - v97:BasicObject = GetLocal :iter_method, l0, EP@4 - v99:BasicObject = Send v95, 0x1070, :__send__, v97 # SendFallbackReason: Send: unsupported method type Optimized - v100:BasicObject = GetLocal :list, l0, EP@6 - v101:BasicObject = GetLocal :sep, l0, EP@5 - v102:BasicObject = GetLocal :iter_method, l0, EP@4 - v103:BasicObject = GetLocal :kwsplat, l0, EP@3 - CheckInterrupts - Return v99 + v110:HashExact[VALUE(0x1068)] = Const Value(VALUE(0x1068)) + SetLocal :kwsplat, l0, EP@3, v110 + v94:BasicObject = GetLocal :list, l0, EP@6 + v96:BasicObject = GetLocal :iter_method, l0, EP@4 + v98:BasicObject = Send v94, 0x1070, :__send__, v96 # SendFallbackReason: Send: unsupported method type Optimized + v99:BasicObject = GetLocal :list, l0, EP@6 + v100:BasicObject = GetLocal :sep, l0, EP@5 + v101:BasicObject = GetLocal :iter_method, l0, EP@4 + v102:BasicObject = GetLocal :kwsplat, l0, EP@3 + CheckInterrupts + Return v98 "); } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 7cdb907fa825cf..9a204d83ecad6c 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2284,20 +2284,20 @@ pub mod hir_build_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v11:BasicObject = GetConstantPath 0x1000 - v13:NilClass = Const Value(nil) - v16:CBool = IsMethodCFunc v11, :new - IfFalse v16, bb4(v6, v13, v11) - v18:HeapBasicObject = ObjectAlloc v11 - v20:BasicObject = Send v18, :initialize # SendFallbackReason: Uncategorized(opt_send_without_block) + v10:BasicObject = GetConstantPath 0x1000 + v12:NilClass = Const Value(nil) + v15:CBool = IsMethodCFunc v10, :new + IfFalse v15, bb4(v6, v12, v10) + v17:HeapBasicObject = ObjectAlloc v10 + v19:BasicObject = Send v17, :initialize # SendFallbackReason: Uncategorized(opt_send_without_block) CheckInterrupts - Jump bb5(v6, v18, v20) - bb4(v24:BasicObject, v25:NilClass, v26:BasicObject): - v29:BasicObject = Send v26, :new # SendFallbackReason: Uncategorized(opt_send_without_block) - Jump bb5(v24, v29, v25) - bb5(v32:BasicObject, v33:BasicObject, v34:BasicObject): + Jump bb5(v6, v17, v19) + bb4(v23:BasicObject, v24:NilClass, v25:BasicObject): + v28:BasicObject = Send v25, :new # SendFallbackReason: Uncategorized(opt_send_without_block) + Jump bb5(v23, v28, v24) + bb5(v31:BasicObject, v32:BasicObject, v33:BasicObject): CheckInterrupts - Return v33 + Return v32 "); } From b479c41dd4fc2a3bd53bba545434eae495aec556 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 24 Feb 2026 09:40:34 +0900 Subject: [PATCH 03/35] Use .bundle gems dependencies directly --- spec/bundled_gems_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/bundled_gems_spec.rb b/spec/bundled_gems_spec.rb index 0c9b45a4ad66dd..7b8ab64f252daf 100644 --- a/spec/bundled_gems_spec.rb +++ b/spec/bundled_gems_spec.rb @@ -24,15 +24,15 @@ def self.ruby=(ruby) require_relative "bundler/support/rubygems_ext" Spec::Helpers.install_dev_bundler FileUtils.mkdir_p Spec::Path.gem_path + + %w[sinatra rack tilt rack-protection rack-session rack-test mustermann base64 logger compact_index].each do |gem| + path, = Dir[File.expand_path("../.bundle/gems/#{gem}-*/lib", __dir__)] + $LOAD_PATH.unshift(path) if path + end end config.around(:each) do |example| FileUtils.cp_r Spec::Path.pristine_system_gem_path, Spec::Path.system_gem_path - FileUtils.mkdir_p Spec::Path.base_system_gem_path.join("gems") - %w[sinatra rack tilt rack-protection rack-session rack-test mustermann base64 logger compact_index].each do |gem| - path, = Dir[File.expand_path("../.bundle/gems/#{gem}-*", __dir__)] - FileUtils.cp_r path, Spec::Path.base_system_gem_path.join("gems") - end with_gem_path_as(system_gem_path) do Bundler.ui.silence { example.run } From 8e9eb698b6638e0f3e76748c756f699e79d2e97d Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Wed, 13 Mar 2024 17:17:20 -0700 Subject: [PATCH 04/35] Freeze singleton class chain The following code: ```ruby x = Object.new sc1 = x.singleton_class sc2 = sc1.singleton_class x.freeze ``` Would freeze sc1 but not sc2, even though sc1 would be frozen. Handle this by walking the class pointer chain for the object. If the class is a singleton class, and it isn't frozen, and the attached object for the singleton class is the object, the singleton class should be frozen, and we move to the next iteration. Fixes [Bug #20319] --- class.c | 22 +++++++++++---------- test/ruby/test_object.rb | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/class.c b/class.c index 81e740b2099ef7..cc4c6845b49977 100644 --- a/class.c +++ b/class.c @@ -2884,16 +2884,18 @@ singleton_class_of(VALUE obj, bool ensure_eigenclass) } void -rb_freeze_singleton_class(VALUE x) -{ - /* should not propagate to meta-meta-class, and so on */ - if (!RCLASS_SINGLETON_P(x)) { - VALUE klass = RBASIC_CLASS(x); - if (klass && // no class when hidden from ObjectSpace - FL_TEST_RAW(klass, FL_SINGLETON) && - !OBJ_FROZEN_RAW(klass)) { - OBJ_FREEZE(klass); - } +rb_freeze_singleton_class(VALUE attached_object) +{ + VALUE klass; + + /* Freeze singleton classes of singleton class, as singleton class is frozen, and so on */ + /* In each iteration, check the current object's class pointer is the singleton class of the object. */ + while ((klass = RBASIC_CLASS(attached_object)) && + FL_TEST_RAW(klass, FL_SINGLETON) && + !OBJ_FROZEN_RAW(klass) && + (RCLASS_ATTACHED_OBJECT(klass) == attached_object)) { + attached_object = klass; + OBJ_FREEZE(attached_object); } } diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index f4dfe2251b884f..2f340788be43b2 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -1025,6 +1025,48 @@ def test_singleton_class_freeze assert_predicate(ys, :frozen?, '[Bug #19169]') end + def test_singleton_class_of_singleton_class_freeze + x = Object.new + xs = x.singleton_class + xxs = xs.singleton_class + xxxs = xxs.singleton_class + x.freeze + assert_predicate(xs, :frozen?, '[Bug #20319]') + assert_predicate(xxs, :frozen?, '[Bug #20319]') + assert_predicate(xxxs, :frozen?, '[Bug #20319]') + + m = Module.new + y = Object.new + ys = y.singleton_class + ys.prepend(Module.new) + yys = ys.singleton_class + yys.prepend(Module.new) + yyys = yys.singleton_class + yyys.prepend(Module.new) + y.freeze + assert_predicate(ys, :frozen?, '[Bug #20319]') + assert_predicate(yys, :frozen?, '[Bug #20319]') + assert_predicate(yyys, :frozen?, '[Bug #20319]') + + c = Class.new + cs = c.singleton_class + ccs = cs.singleton_class + cccs = ccs.singleton_class + d = Class.new(c) + ds = d.singleton_class + dds = ds.singleton_class + ddds = dds.singleton_class + d.freeze + assert_predicate(d, :frozen?, '[Bug #20319]') + assert_predicate(ds, :frozen?, '[Bug #20319]') + assert_predicate(dds, :frozen?, '[Bug #20319]') + assert_predicate(ddds, :frozen?, '[Bug #20319]') + assert_not_predicate(c, :frozen?, '[Bug #20319]') + assert_not_predicate(cs, :frozen?, '[Bug #20319]') + assert_not_predicate(ccs, :frozen?, '[Bug #20319]') + assert_not_predicate(cccs, :frozen?, '[Bug #20319]') + end + def test_redef_method_missing bug5473 = '[ruby-core:40287]' ['ArgumentError.new("bug5473")', 'ArgumentError, "bug5473"', '"bug5473"'].each do |code| From 997ac19b74259b13f777a548b813a0f2c2228554 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Mon, 23 Feb 2026 20:06:32 -0700 Subject: [PATCH 05/35] ZJIT: Remove redundant PatchPoints within basic blocks (#16231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new optimization pass that deduplicates PatchPoint instructions asserting the same invariant within a basic block when no intervening instruction could invalidate it. This is common in practice — e.g., `1 + 2 + 3` produces two identical `PatchPoint MethodRedefined(Integer, +, cme)` because each `+` call inserts one. The second is redundant since nothing between them can redefine Integer#+. The approach adds a `PatchPoint` leaf to the abstract heap DAG so the effect system can distinguish instructions that could invalidate invariants (writes to PatchPoint, e.g. Send/CCall) from those that cannot (e.g. IncrCounter, Const). The pass maintains a HashSet of seen invariants per block and skips duplicates, clearing the set when an instruction writes to the PatchPoint heap. Removed PatchPoints' orphaned Snapshot dependencies are cleaned up by the subsequent DCE pass. I added a new test with snapshots to `zjit/src/hir/tests.rs` in the first commit so that you can see how the change affects it in the second commit: https://github.com/ruby/ruby/pull/16231/changes/88cf26b6380cdf577522d11b4f3c838fed2a71b3#diff-38be021d8d0035cca77d3eab5ccc530109e7832f4711a5beb4dfced7001787ae closes https://github.com/Shopify/ruby/issues/779 --- zjit/src/hir.rs | 38 +++++++++++++++++++++------ zjit/src/hir/opt_tests.rs | 17 ------------ zjit/src/hir/tests.rs | 35 ++++++++++++++++++++++++ zjit/src/hir_effect/gen_hir_effect.rb | 1 + zjit/src/hir_effect/hir_effect.inc.rs | 12 ++++++--- zjit/src/stats.rs | 1 + 6 files changed, 75 insertions(+), 29 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7d98dce666b874..ae316d409b0287 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -118,7 +118,7 @@ impl std::fmt::Display for BranchEdge { } /// Invalidation reasons -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Invariant { /// Basic operation is redefined BOPRedefined { @@ -1237,10 +1237,10 @@ impl Insn { Insn::GuardNoBitsSet { .. } => effects::Any, Insn::GuardGreaterEq { .. } => effects::Any, Insn::GuardLess { .. } => effects::Any, - Insn::PatchPoint { .. } => effects::Any, + Insn::PatchPoint { .. } => Effect::read_write(abstract_heaps::PatchPoint, abstract_heaps::Control), Insn::SideExit { .. } => effects::Any, - Insn::IncrCounter(_) => effects::Any, - Insn::IncrCounterPtr { .. } => effects::Any, + Insn::IncrCounter(_) => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Other), + Insn::IncrCounterPtr { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Other), Insn::CheckInterrupts { .. } => effects::Any, Insn::InvokeProc { .. } => effects::Any, Insn::RefineType { .. } => effects::Empty, @@ -5037,6 +5037,29 @@ impl Function { } } + /// Remove duplicate PatchPoint instructions within each basic block. + /// Two PatchPoints are redundant if they assert the same Invariant and no + /// intervening instruction could invalidate it (i.e., writes to PatchPoint). + fn remove_redundant_patch_points(&mut self) { + for block_id in self.rpo() { + let mut seen = HashSet::new(); + let insns = std::mem::take(&mut self.blocks[block_id.0].insns); + let mut new_insns = Vec::with_capacity(insns.len()); + for insn_id in insns { + let insn = self.find(insn_id); + if let Insn::PatchPoint { invariant, .. } = insn { + if !seen.insert(invariant) { + continue; + } + } else if insn.effects_of().write_bits().overlaps(abstract_heaps::PatchPoint) { + seen.clear(); + } + new_insns.push(insn_id); + } + self.blocks[block_id.0].insns = new_insns; + } + } + /// Return a list that has entry_block and then jit_entry_blocks fn entry_blocks(&self) -> Vec { let mut entry_blocks = self.jit_entry_blocks.clone(); @@ -5260,6 +5283,8 @@ impl Function { Counter::compile_hir_fold_constants_time_ns } else if ident_equal!($name, clean_cfg) { Counter::compile_hir_clean_cfg_time_ns + } else if ident_equal!($name, remove_redundant_patch_points) { + Counter::compile_hir_remove_redundant_patch_points_time_ns } else if ident_equal!($name, eliminate_dead_code) { Counter::compile_hir_eliminate_dead_code_time_ns } else { @@ -5286,6 +5311,7 @@ impl Function { run_pass!(optimize_c_calls); run_pass!(fold_constants); run_pass!(clean_cfg); + run_pass!(remove_redundant_patch_points); run_pass!(eliminate_dead_code); if should_dump { @@ -8473,8 +8499,6 @@ mod graphviz_tests { bb3 [label=< - - @@ -8536,7 +8560,6 @@ mod graphviz_tests { -
bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject) 
PatchPoint NoTracePoint 
PatchPoint NoTracePoint 
PatchPoint NoTracePoint 
PatchPoint MethodRedefined(Integer@0x1000, |@0x1008, cme:0x1010) 
v27:Fixnum = GuardType v11, Fixnum 
v28:Fixnum = GuardType v12, Fixnum 
v18:Truthy = RefineType v9, Truthy 
PatchPoint NoTracePoint 
v21:Fixnum[3] = Const Value(3) 
PatchPoint NoTracePoint 
CheckInterrupts 
Return v21 
>]; @@ -8545,7 +8568,6 @@ mod graphviz_tests { bb4(v26:BasicObject, v27:Falsy)  PatchPoint NoTracePoint  v31:Fixnum[4] = Const Value(4)  - PatchPoint NoTracePoint  CheckInterrupts  Return v31  >]; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 7b831b1484f1f3..94c70d29031e94 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -115,10 +115,7 @@ mod hir_opt_tests { v10:Fixnum[1] = Const Value(1) v12:Fixnum[2] = Const Value(2) PatchPoint MethodRedefined(Integer@0x1000, +@0x1008, cme:0x1010) - v34:Fixnum[3] = Const Value(3) IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[3] = Const Value(3) - PatchPoint MethodRedefined(Integer@0x1000, +@0x1008, cme:0x1010) v35:Fixnum[6] = Const Value(6) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -147,10 +144,7 @@ mod hir_opt_tests { v10:Fixnum[5] = Const Value(5) v12:Fixnum[3] = Const Value(3) PatchPoint MethodRedefined(Integer@0x1000, -@0x1008, cme:0x1010) - v34:Fixnum[2] = Const Value(2) IncrCounter inline_cfunc_optimized_send_count - v17:Fixnum[1] = Const Value(1) - PatchPoint MethodRedefined(Integer@0x1000, -@0x1008, cme:0x1010) v35:Fixnum[1] = Const Value(1) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -3063,13 +3057,10 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) v26:Class[String@0x1008] = Const Value(VALUE(0x1008)) - PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1010, Class) v29:Class[Class@0x1018] = Const Value(VALUE(0x1018)) - PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1020, Module) v32:Class[Module@0x1028] = Const Value(VALUE(0x1028)) - PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1030, BasicObject) v35:Class[BasicObject@0x1038] = Const Value(VALUE(0x1038)) v18:ArrayExact = NewArray v26, v29, v32, v35 @@ -3098,7 +3089,6 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Enumerable) v22:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1010, Kernel) v25:ModuleExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) v14:ArrayExact = NewArray v22, v25 @@ -5226,7 +5216,6 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) v11:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(HASH_REDEFINED_OP_FLAG, BOP_FREEZE) CheckInterrupts Return v11 "); @@ -5323,7 +5312,6 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v11:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) CheckInterrupts Return v11 "); @@ -5420,7 +5408,6 @@ mod hir_opt_tests { bb3(v6:BasicObject): PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) v11:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint BOPRedefined(STRING_REDEFINED_OP_FLAG, BOP_FREEZE) CheckInterrupts Return v11 "); @@ -7222,7 +7209,6 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, A) v35:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1010, B) v38:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) PatchPoint NoSingletonClass(Array@0x1020) @@ -11514,7 +11500,6 @@ mod hir_opt_tests { v15:TrueClass = Const Value(true) PatchPoint NoSingletonClass(Class@0x1040) PatchPoint MethodRedefined(Class@0x1040, respond_to?@0x1048, cme:0x1050) - PatchPoint NoSingletonClass(Class@0x1040) PatchPoint MethodRedefined(Class@0x1040, _lex_actions@0x1078, cme:0x1080) v55:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -12098,10 +12083,8 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(TestNestedAccess@0x1010) PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020) v52:Fixnum[100] = Const Value(100) - PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1048, NESTED_FROZEN) v33:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestNestedAccess@0x1010) PatchPoint MethodRedefined(TestNestedAccess@0x1010, y@0x1050, cme:0x1058) v54:Fixnum[200] = Const Value(200) PatchPoint MethodRedefined(Integer@0x1080, +@0x1088, cme:0x1090) diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 9a204d83ecad6c..bcfecaa38867b0 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -24,6 +24,41 @@ mod snapshot_tests { format!("{}", FunctionPrinter::with_snapshot(&function)) } + #[test] + fn test_remove_redundant_patch_points() { + eval(" + def test = 1 + 2 + 3 + test + test + "); + assert_snapshot!(optimized_hir_string("test"), @r" + fn test@:2: + bb0(): + Entries bb1, bb2 + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb3(v1) + bb2(): + EntryPoint JIT(0) + v4:BasicObject = LoadArg :self@0 + Jump bb3(v4) + bb3(v6:BasicObject): + v8:Any = Snapshot FrameState { pc: 0x1000, stack: [], locals: [] } + PatchPoint NoTracePoint + v10:Fixnum[1] = Const Value(1) + v12:Fixnum[2] = Const Value(2) + v13:Any = Snapshot FrameState { pc: 0x1008, stack: [v10, v12], locals: [] } + PatchPoint MethodRedefined(Integer@0x1010, +@0x1018, cme:0x1020) + IncrCounter inline_cfunc_optimized_send_count + v35:Fixnum[6] = Const Value(6) + IncrCounter inline_cfunc_optimized_send_count + v21:Any = Snapshot FrameState { pc: 0x1048, stack: [v35], locals: [] } + CheckInterrupts + Return v35 + "); + } + #[test] fn test_new_array_with_elements() { eval("def test(a, b) = [a, b]"); diff --git a/zjit/src/hir_effect/gen_hir_effect.rb b/zjit/src/hir_effect/gen_hir_effect.rb index 5d13ebafa2e185..cd4ca73c103a03 100644 --- a/zjit/src/hir_effect/gen_hir_effect.rb +++ b/zjit/src/hir_effect/gen_hir_effect.rb @@ -48,6 +48,7 @@ def to_graphviz effect, f allocator = any.subeffect 'Allocator' control = any.subeffect 'Control' memory = any.subeffect 'Memory' +patch_point = any.subeffect 'PatchPoint' other = memory.subeffect 'Other' frame = memory.subeffect 'Frame' pc = frame.subeffect 'PC' diff --git a/zjit/src/hir_effect/hir_effect.inc.rs b/zjit/src/hir_effect/hir_effect.inc.rs index d9566b3eaa5574..7c5fc53ab3bb46 100644 --- a/zjit/src/hir_effect/hir_effect.inc.rs +++ b/zjit/src/hir_effect/hir_effect.inc.rs @@ -1,7 +1,7 @@ // This file is @generated by src/hir/gen_hir_effect.rb. mod bits { pub const Allocator: u8 = 1u8 << 0; - pub const Any: u8 = Allocator | Control | Memory; + pub const Any: u8 = Allocator | Control | Memory | PatchPoint; pub const Control: u8 = 1u8 << 1; pub const Empty: u8 = 0u8; pub const Frame: u8 = Locals | PC | Stack; @@ -9,12 +9,14 @@ mod bits { pub const Memory: u8 = Frame | Other; pub const Other: u8 = 1u8 << 3; pub const PC: u8 = 1u8 << 4; - pub const Stack: u8 = 1u8 << 5; - pub const AllBitPatterns: [(&str, u8); 10] = [ + pub const PatchPoint: u8 = 1u8 << 5; + pub const Stack: u8 = 1u8 << 6; + pub const AllBitPatterns: [(&str, u8); 11] = [ ("Any", Any), ("Memory", Memory), ("Frame", Frame), ("Stack", Stack), + ("PatchPoint", PatchPoint), ("PC", PC), ("Other", Other), ("Locals", Locals), @@ -22,7 +24,7 @@ mod bits { ("Allocator", Allocator), ("Empty", Empty), ]; - pub const NumEffectBits: u8 = 6; + pub const NumEffectBits: u8 = 7; } pub mod effect_types { pub type EffectBits = u8; @@ -38,6 +40,7 @@ pub mod abstract_heaps { pub const Memory: AbstractHeap = AbstractHeap::from_bits(bits::Memory); pub const Other: AbstractHeap = AbstractHeap::from_bits(bits::Other); pub const PC: AbstractHeap = AbstractHeap::from_bits(bits::PC); + pub const PatchPoint: AbstractHeap = AbstractHeap::from_bits(bits::PatchPoint); pub const Stack: AbstractHeap = AbstractHeap::from_bits(bits::Stack); } pub mod effects { @@ -51,5 +54,6 @@ pub mod effects { pub const Memory: Effect = Effect::promote(abstract_heaps::Memory); pub const Other: Effect = Effect::promote(abstract_heaps::Other); pub const PC: Effect = Effect::promote(abstract_heaps::PC); + pub const PatchPoint: Effect = Effect::promote(abstract_heaps::PatchPoint); pub const Stack: Effect = Effect::promote(abstract_heaps::Stack); } diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 23ff9b16cf77e1..059964b8e8dd07 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -169,6 +169,7 @@ make_counters! { compile_hir_strength_reduce_time_ns, compile_hir_fold_constants_time_ns, compile_hir_clean_cfg_time_ns, + compile_hir_remove_redundant_patch_points_time_ns, compile_hir_eliminate_dead_code_time_ns, compile_lir_time_ns, } From fc16000e92766c3cdf863cddc460815303372a65 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 23 Feb 2026 18:28:05 +0900 Subject: [PATCH 06/35] CI: Highlight diff of depend files [ci skip] --- .github/workflows/check_dependencies.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 1ba1b0388d6376..b84ecaa88b65a7 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -50,7 +50,7 @@ jobs: - run: make fix-depends - - run: git diff --no-ext-diff --ignore-submodules --exit-code + - run: git diff --color --no-ext-diff --ignore-submodules --exit-code - uses: ./.github/actions/slack with: From 70c625148892553c4dd50adefda955d45b46ff47 Mon Sep 17 00:00:00 2001 From: "NARUSE, Yui" Date: Tue, 24 Feb 2026 14:27:52 +0900 Subject: [PATCH 07/35] Use rev-parse HEAD instead of log --format=%H -1 --- defs/gmake.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index 475ad61602d4aa..ea12729f148e92 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -276,7 +276,7 @@ pull-github: fetch-github $(call pull-github,$(PR)) define pull-github - $(eval GITHUB_MERGE_BASE := $(shell $(GIT_LOG_FORMAT)%H -1) + $(eval GITHUB_MERGE_BASE := $(shell $(GIT) rev-parse HEAD) $(eval GITHUB_MERGE_BRANCH := $(shell $(GIT_IN_SRC) symbolic-ref --short HEAD)) $(eval GITHUB_MERGE_WORKTREE := $(shell mktemp -d "$(srcdir)/gh-$(1)-XXXXXX")) $(GIT_IN_SRC) worktree prune @@ -434,7 +434,7 @@ ifneq ($(DOT_WAIT),) endif ifeq ($(HAVE_GIT),yes) -REVISION_LATEST := $(shell $(GIT_LOG_FORMAT)%H -1 2>/dev/null) +REVISION_LATEST := $(shell $(GIT) rev-parse HEAD) else REVISION_LATEST := update endif From 682f36ab1fd0468a0eb42946d53809e942ab2141 Mon Sep 17 00:00:00 2001 From: git Date: Tue, 24 Feb 2026 07:08:36 +0000 Subject: [PATCH 08/35] Update bundled gems list as of 2026-02-24 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 90119c910b2b6e..100c4d72f19383 100644 --- a/NEWS.md +++ b/NEWS.md @@ -68,7 +68,7 @@ releases. ### The following bundled gems are updated. -* minitest 6.0.1 +* minitest 6.0.2 * test-unit 3.7.7 * rss 0.3.2 * net-imap 0.6.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index d27f29682153b0..a9262ffa1236b6 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,7 +6,7 @@ # - revision: revision in repository-url to test # if `revision` is not given, "v"+`version` or `version` will be used. -minitest 6.0.1 https://github.com/minitest/minitest +minitest 6.0.2 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.7 https://github.com/test-unit/test-unit From ebd57856c2a794a7b4c43402deb455cbc36f3c81 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:53:43 +0100 Subject: [PATCH 09/35] [ruby/prism] Fix some locations in the the config https://github.com/ruby/prism/commit/64e29baac6 --- prism/config.yml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/prism/config.yml b/prism/config.yml index 7527e01070b2a5..472ab291a706f9 100644 --- a/prism/config.yml +++ b/prism/config.yml @@ -1046,19 +1046,19 @@ nodes: Represents an array pattern in pattern matching. foo in 1, 2 - ^^^^^^^^^^^ + ^^^^ foo in [1, 2] - ^^^^^^^^^^^^^ + ^^^^^^ foo in *bar - ^^^^^^^^^^^ + ^^^^ foo in Bar[] - ^^^^^^^^^^^^ + ^^^^^ foo in Bar[1, 2, 3] - ^^^^^^^^^^^^^^^^^^^ + ^^^^^^^^^^^^ - name: AssocNode fields: - name: key @@ -1167,7 +1167,7 @@ nodes: Represents the else clause within the begin block. begin x; rescue y; else z; end - ^^^^^^ + ^^^^^^^^^^^ - name: ensure_clause type: node? kind: EnsureNode @@ -1200,7 +1200,7 @@ nodes: The expression that is being passed as a block argument. This can be any [non-void expression](https://github.com/ruby/prism/blob/main/docs/parsing_rules.md#non-void-expression). foo(&args) - ^^^^^ + ^^^^ - name: operator_loc type: location comment: | @@ -1212,7 +1212,7 @@ nodes: Represents a block argument using `&`. bar(&args) - ^^^^^^^^^^ + ^^^^^ - name: BlockLocalVariableNode flags: ParameterFlags fields: @@ -1753,7 +1753,7 @@ nodes: Represents assigning to a local variable in pattern matching. foo => [bar => baz] - ^^^^^^^^^^^^ + ^^^^^^^^^^ - name: CaseMatchNode fields: - name: predicate @@ -1763,7 +1763,7 @@ nodes: Represents the predicate of the case match. This can be either `nil` or any [non-void expressions](https://github.com/ruby/prism/blob/main/docs/parsing_rules.md#non-void-expression). case true; in false; end - ^^^^ + ^^^^ - name: conditions type: node[] kind: InNode @@ -1779,7 +1779,7 @@ nodes: Represents the else clause of the case match. case true; in false; else; end - ^^^^ + ^^^^^^^^^ - name: case_keyword_loc type: location comment: | @@ -1826,7 +1826,7 @@ nodes: Represents the else clause of the case statement. case true; when false; else; end - ^^^^ + ^^^^^^^^^ - name: case_keyword_loc type: location comment: | @@ -1888,9 +1888,8 @@ nodes: comment: | Represents the body of the class. - class Foo - foo - ^^^ + class Foo; bar; end + ^^^ - name: end_keyword_loc type: location comment: | @@ -4635,7 +4634,7 @@ nodes: The else clause of the unless expression, if present. unless cond then bar else baz end - ^^^^^^^^ + ^^^^^^^^^^^^ - name: end_keyword_loc type: location? comment: | From 3aa89271b7b585f3623ceaad563f98f7d04643da Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Sun, 1 Feb 2026 18:32:16 +0100 Subject: [PATCH 10/35] [ruby/prism] Introduce xfree_sized and xrealloc_sized This will allow prism to pass buffer sizes to the Ruby GC. It also helps avoid buffer overflow as it confirms the size was correctly tracked all the way until the buffer is freed. https://github.com/ruby/prism/commit/a5c3ee3e4c --- lib/prism/prism.gemspec | 1 + prism/debug_allocator.h | 99 ++++++++++++++++++++++++ prism/defines.h | 32 +++++++- prism/extension.c | 4 +- prism/options.c | 4 +- prism/prism.c | 53 ++++++------- prism/static_literals.c | 4 +- prism/templates/ext/prism/api_node.c.erb | 2 +- prism/templates/src/diagnostic.c.erb | 4 +- prism/templates/src/node.c.erb | 10 ++- prism/util/pm_buffer.c | 5 +- prism/util/pm_constant_pool.c | 19 +++-- prism/util/pm_integer.c | 17 ++-- prism/util/pm_line_offset_list.c | 4 +- prism/util/pm_list.c | 2 +- prism/util/pm_string.c | 15 ++-- 16 files changed, 206 insertions(+), 69 deletions(-) create mode 100644 prism/debug_allocator.h diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index a155dc3da4689f..dde7e711e2e580 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -48,6 +48,7 @@ Gem::Specification.new do |spec| "ext/prism/extension.h", "include/prism.h", "include/prism/ast.h", + "include/prism/debug_allocator.h", "include/prism/defines.h", "include/prism/diagnostic.h", "include/prism/encoding.h", diff --git a/prism/debug_allocator.h b/prism/debug_allocator.h new file mode 100644 index 00000000000000..3e28a95efbcae7 --- /dev/null +++ b/prism/debug_allocator.h @@ -0,0 +1,99 @@ +/** + * @file debug_allocator.h + * + * Decorate allocation function to ensure sizes are correct. + */ +#ifndef PRISM_DEBUG_ALLOCATOR_H +#define PRISM_DEBUG_ALLOCATOR_H + +#include +#include +#include + +static inline void * +pm_debug_malloc(size_t size) +{ + size_t *memory = xmalloc(size + sizeof(size_t)); + memory[0] = size; + return memory + 1; +} + +static inline void * +pm_debug_calloc(size_t nmemb, size_t size) +{ + size_t total_size = nmemb * size; + void *ptr = pm_debug_malloc(total_size); + memset(ptr, 0, total_size); + return ptr; +} + +static inline void * +pm_debug_realloc(void *ptr, size_t size) +{ + if (ptr == NULL) { + return pm_debug_malloc(size); + } + + size_t *memory = (size_t *)ptr; + void *raw_memory = memory - 1; + memory = (size_t *)xrealloc(raw_memory, size + sizeof(size_t)); + memory[0] = size; + return memory + 1; +} + +static inline void +pm_debug_free(void *ptr) +{ + if (ptr != NULL) { + size_t *memory = (size_t *)ptr; + xfree(memory - 1); + } +} + +static inline void +pm_debug_free_sized(void *ptr, size_t old_size) +{ + if (ptr != NULL) { + size_t *memory = (size_t *)ptr; + if (old_size != memory[-1]) { + fprintf(stderr, "[BUG] buffer %p was allocated with size %lu but freed with size %lu\n", ptr, memory[-1], old_size); + abort(); + } + xfree_sized(memory - 1, old_size + sizeof(size_t)); + } +} + +static inline void * +pm_debug_realloc_sized(void *ptr, size_t size, size_t old_size) +{ + if (ptr == NULL) { + if (old_size != 0) { + fprintf(stderr, "[BUG] realloc_sized called with NULL pointer and old size %lu\n", old_size); + abort(); + } + return pm_debug_malloc(size); + } + + size_t *memory = (size_t *)ptr; + if (old_size != memory[-1]) { + fprintf(stderr, "[BUG] buffer %p was allocated with size %lu but realloced with size %lu\n", ptr, memory[-1], old_size); + abort(); + } + return pm_debug_realloc(ptr, size); +} + +#undef xmalloc +#undef xrealloc +#undef xcalloc +#undef xfree +#undef xrealloc_sized +#undef xfree_sized + +#define xmalloc pm_debug_malloc +#define xrealloc pm_debug_realloc +#define xcalloc pm_debug_calloc +#define xfree pm_debug_free +#define xrealloc_sized pm_debug_realloc_sized +#define xfree_sized pm_debug_free_sized + +#endif diff --git a/prism/defines.h b/prism/defines.h index f2e814fa1dfcd8..1c4e5fa053dabf 100644 --- a/prism/defines.h +++ b/prism/defines.h @@ -161,10 +161,12 @@ * ``` * #ifndef PRISM_XALLOCATOR_H * #define PRISM_XALLOCATOR_H - * #define xmalloc my_malloc - * #define xrealloc my_realloc - * #define xcalloc my_calloc - * #define xfree my_free + * #define xmalloc my_malloc + * #define xrealloc my_realloc + * #define xcalloc my_calloc + * #define xfree my_free + * #define xrealloc_sized my_realloc_sized // (optional) + * #define xfree_sized my_free_sized // (optional) * #endif * ``` */ @@ -204,6 +206,28 @@ #endif #endif +#ifndef xfree_sized +/** + * The free_sized function that should be used. This can be overridden with the + * PRISM_XALLOCATOR define. + * If not defined, defaults to calling xfree. + */ + #define xfree_sized(p, s) xfree(((void)(s), (p))) +#endif + +#ifndef xrealloc_sized +/** + * The xrealloc_sized function that should be used. This can be overridden with the + * PRISM_XALLOCATOR define. + * If not defined, defaults to calling xrealloc. + */ + #define xrealloc_sized(p, ns, os) xrealloc((p), ((void)(os), (ns))) +#endif + +#ifdef PRISM_BUILD_DEBUG + #include "prism/debug_allocator.h" +#endif + /** * If PRISM_BUILD_MINIMAL is defined, then we're going to define every possible * switch that will turn off certain features of prism. diff --git a/prism/extension.c b/prism/extension.c index 03182c1a5747c7..fcbc1e6c244fa2 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -413,7 +413,7 @@ dump(int argc, VALUE *argv, VALUE self) { if (options.freeze) rb_obj_freeze(value); #ifdef PRISM_BUILD_DEBUG - xfree(dup); + xfree_sized(dup, length); #endif pm_string_free(&input); @@ -929,7 +929,7 @@ parse(int argc, VALUE *argv, VALUE self) { VALUE value = parse_input(&input, &options); #ifdef PRISM_BUILD_DEBUG - xfree(dup); + xfree_sized(dup, length); #endif pm_string_free(&input); diff --git a/prism/options.c b/prism/options.c index 09d2a65a6cbcf6..961d52330f8e35 100644 --- a/prism/options.c +++ b/prism/options.c @@ -226,10 +226,10 @@ pm_options_free(pm_options_t *options) { pm_string_free(&scope->locals[local_index]); } - xfree(scope->locals); + xfree_sized(scope->locals, scope->locals_count * sizeof(pm_string_t)); } - xfree(options->scopes); + xfree_sized(options->scopes, options->scopes_count * sizeof(pm_options_scope_t)); } /** diff --git a/prism/prism.c b/prism/prism.c index 4ab1ca4594db98..bfa91f4296cdbf 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -286,7 +286,7 @@ lex_mode_pop(pm_parser_t *parser) { } else { parser->lex_modes.index--; pm_lex_mode_t *prev = parser->lex_modes.current->prev; - xfree(parser->lex_modes.current); + xfree_sized(parser->lex_modes.current, sizeof(pm_lex_mode_t)); parser->lex_modes.current = prev; } } @@ -777,7 +777,7 @@ pm_parser_scope_shareable_constant_set(pm_parser_t *parser, pm_shareable_constan static void pm_locals_free(pm_locals_t *locals) { if (locals->capacity > 0) { - xfree(locals->locals); + xfree_sized(locals->locals, locals->capacity * sizeof(pm_local_t)); } } @@ -2956,7 +2956,7 @@ pm_call_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3010,7 +3010,7 @@ pm_index_and_write_node_create(pm_parser_t *parser, pm_call_node_t *target, cons // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3040,7 +3040,7 @@ pm_call_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3071,7 +3071,7 @@ pm_index_operator_write_node_create(pm_parser_t *parser, pm_call_node_t *target, // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3101,7 +3101,7 @@ pm_call_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3132,7 +3132,7 @@ pm_index_or_write_node_create(pm_parser_t *parser, pm_call_node_t *target, const // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3164,7 +3164,7 @@ pm_call_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3192,7 +3192,7 @@ pm_index_target_node_create(pm_parser_t *parser, pm_call_node_t *target) { // Here we're going to free the target, since it is no longer necessary. // However, we don't want to call `pm_node_destroy` because we want to keep // around all of its children since we just reused them. - xfree(target); + xfree_sized(target, sizeof(pm_call_node_t)); return node; } @@ -3873,7 +3873,8 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) { // First, get a buffer of the content. size_t length = (size_t) diff; - char *buffer = xmalloc(sizeof(char) * (length + 1)); + const size_t buffer_size = sizeof(char) * (length + 1); + char *buffer = xmalloc(buffer_size); memcpy((void *) buffer, token->start, length); // Next, determine if we need to replace the decimal point because of @@ -3908,7 +3909,7 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) { // is in a valid format. However it's good to be safe. if ((eptr != buffer + length) || (errno != 0 && errno != ERANGE)) { PM_PARSER_ERR_TOKEN_FORMAT_CONTENT(parser, token, PM_ERR_FLOAT_PARSE); - xfree((void *) buffer); + xfree_sized(buffer, buffer_size); return 0.0; } @@ -3931,7 +3932,7 @@ pm_double_parse(pm_parser_t *parser, const pm_token_t *token) { } // Finally we can free the buffer and return the value. - xfree((void *) buffer); + xfree_sized(buffer, buffer_size); return value; } @@ -4017,7 +4018,7 @@ pm_float_node_rational_create(pm_parser_t *parser, const pm_token_t *token) { digits[0] = '1'; if (fract_length > 1) memset(digits + 1, '0', fract_length - 1); pm_integer_parse(&node->denominator, PM_INTEGER_BASE_DEFAULT, digits, digits + fract_length); - xfree(digits); + xfree_sized(digits, length); pm_integers_reduce(&node->numerator, &node->denominator); return node; @@ -5521,7 +5522,7 @@ pm_multi_write_node_create(pm_parser_t *parser, pm_multi_target_node_t *target, // Explicitly do not call pm_node_destroy here because we want to keep // around all of the information within the MultiWriteNode node. - xfree(target); + xfree_sized(target, sizeof(pm_multi_target_node_t)); return node; } @@ -5646,7 +5647,7 @@ pm_numbered_reference_read_node_number(pm_parser_t *parser, const pm_token_t *to value = 0; } - xfree(digits); + xfree_sized(digits, sizeof(char) * (length + 1)); if ((errno == ERANGE) || (value > NTH_REF_MAX)) { PM_PARSER_WARN_FORMAT(parser, U32(start - parser->start), U32(length), PM_WARN_INVALID_NUMBERED_REFERENCE, (int) (length + 1), (const char *) token->start); @@ -6751,7 +6752,7 @@ pm_string_node_to_symbol_node(pm_parser_t *parser, pm_string_node_t *node, const // We are explicitly _not_ using pm_node_destroy here because we don't want // to trash the unescaped string. We could instead copy the string if we // know that it is owned, but we're taking the fast path for now. - xfree(node); + xfree_sized(node, sizeof(pm_string_node_t)); return new_node; } @@ -6784,7 +6785,7 @@ pm_symbol_node_to_string_node(pm_parser_t *parser, pm_symbol_node_t *node) { // We are explicitly _not_ using pm_node_destroy here because we don't want // to trash the unescaped string. We could instead copy the string if we // know that it is owned, but we're taking the fast path for now. - xfree(node); + xfree_sized(node, sizeof(pm_symbol_node_t)); return new_node; } @@ -7247,7 +7248,7 @@ pm_parser_scope_pop(pm_parser_t *parser) { parser->current_scope = scope->previous; pm_locals_free(&scope->locals); pm_node_list_free(&scope->implicit_parameters); - xfree(scope); + xfree_sized(scope, sizeof(pm_scope_t)); } /******************************************************************************/ @@ -7847,7 +7848,7 @@ context_push(pm_parser_t *parser, pm_context_t context) { static void context_pop(pm_parser_t *parser) { pm_context_node_t *prev = parser->current_context->prev; - xfree(parser->current_context); + xfree_sized(parser->current_context, sizeof(pm_context_node_t)); parser->current_context = prev; } @@ -16639,7 +16640,7 @@ parse_pattern_hash(pm_parser_t *parser, pm_constant_id_list_t *captures, pm_node } pm_hash_pattern_node_t *node = pm_hash_pattern_node_node_list_create(parser, &assocs, rest); - xfree(assocs.nodes); + xfree_sized(assocs.nodes, assocs.capacity * sizeof(pm_node_t *)); pm_static_literals_free(&keys); return node; @@ -17166,7 +17167,7 @@ parse_pattern(pm_parser_t *parser, pm_constant_id_list_t *captures, uint8_t flag } } - xfree(nodes.nodes); + xfree_sized(nodes.nodes, nodes.capacity * sizeof(pm_node_t *)); } else if (leading_rest) { // Otherwise, if we parsed a single splat pattern, then we know we have // an array pattern, so we can go ahead and create that node. @@ -19453,7 +19454,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_symbol_node_append(interpolated, first_string); pm_interpolated_symbol_node_append(interpolated, second_string); - xfree(current); + xfree_sized(current, sizeof(pm_symbol_node_t)); current = UP(interpolated); } else { assert(false && "unreachable"); @@ -19538,7 +19539,7 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power, b pm_interpolated_symbol_node_append(interpolated, first_string); pm_interpolated_symbol_node_append(interpolated, second_string); - xfree(current); + xfree_sized(current, sizeof(pm_symbol_node_t)); current = UP(interpolated); } else { assert(false && "unreachable"); @@ -22252,7 +22253,7 @@ pm_comment_list_free(pm_list_t *list) { next = node->next; pm_comment_t *comment = (pm_comment_t *) node; - xfree(comment); + xfree_sized(comment, sizeof(pm_comment_t)); } } @@ -22267,7 +22268,7 @@ pm_magic_comment_list_free(pm_list_t *list) { next = node->next; pm_magic_comment_t *magic_comment = (pm_magic_comment_t *) node; - xfree(magic_comment); + xfree_sized(magic_comment, sizeof(pm_magic_comment_t)); } } diff --git a/prism/static_literals.c b/prism/static_literals.c index 58a08a8c03ca11..f3a5650d314d83 100644 --- a/prism/static_literals.c +++ b/prism/static_literals.c @@ -183,7 +183,7 @@ pm_node_hash_insert(pm_node_hash_t *hash, const pm_static_literals_metadata_t *m } // Finally, free the old node list and update the hash. - xfree(hash->nodes); + xfree_sized(hash->nodes, hash->capacity * sizeof(pm_node_t *)); hash->nodes = new_nodes; hash->capacity = new_capacity; } @@ -221,7 +221,7 @@ pm_node_hash_insert(pm_node_hash_t *hash, const pm_static_literals_metadata_t *m */ static void pm_node_hash_free(pm_node_hash_t *hash) { - if (hash->capacity > 0) xfree(hash->nodes); + if (hash->capacity > 0) xfree_sized(hash->nodes, hash->capacity * sizeof(pm_node_t *)); } /** diff --git a/prism/templates/ext/prism/api_node.c.erb b/prism/templates/ext/prism/api_node.c.erb index 69f70240f87b66..e3bcf116cc900a 100644 --- a/prism/templates/ext/prism/api_node.c.erb +++ b/prism/templates/ext/prism/api_node.c.erb @@ -113,7 +113,7 @@ pm_node_stack_pop(pm_node_stack_node_t **stack) { const pm_node_t *visit = current->visit; *stack = current->prev; - xfree(current); + xfree_sized(current, sizeof(pm_node_stack_node_t)); return visit; } diff --git a/prism/templates/src/diagnostic.c.erb b/prism/templates/src/diagnostic.c.erb index 88f8525f8008fb..a11c7893d3acda 100644 --- a/prism/templates/src/diagnostic.c.erb +++ b/prism/templates/src/diagnostic.c.erb @@ -488,7 +488,7 @@ pm_diagnostic_list_append_format(pm_list_t *list, uint32_t start, uint32_t lengt size_t message_length = (size_t) (result + 1); char *message = (char *) xmalloc(message_length); if (message == NULL) { - xfree(diagnostic); + xfree_sized(diagnostic, sizeof(pm_diagnostic_t)); return false; } @@ -519,7 +519,7 @@ pm_diagnostic_list_free(pm_list_t *list) { pm_diagnostic_t *next = (pm_diagnostic_t *) node->node.next; if (node->owned) xfree((void *) node->message); - xfree(node); + xfree_sized(node, sizeof(pm_diagnostic_t)); node = next; } diff --git a/prism/templates/src/node.c.erb b/prism/templates/src/node.c.erb index f1709a0249c3d7..e42a8e5b700e2d 100644 --- a/prism/templates/src/node.c.erb +++ b/prism/templates/src/node.c.erb @@ -32,7 +32,11 @@ pm_node_list_grow(pm_node_list_t *list, size_t size) { next_capacity = double_capacity; } - pm_node_t **nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * next_capacity); + pm_node_t **nodes = (pm_node_t **) xrealloc_sized( + list->nodes, + sizeof(pm_node_t *) * next_capacity, + sizeof(pm_node_t *) * list->capacity + ); if (nodes == NULL) return false; list->nodes = nodes; @@ -79,7 +83,7 @@ pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other) { void pm_node_list_free(pm_node_list_t *list) { if (list->capacity > 0) { - xfree(list->nodes); + xfree_sized(list->nodes, sizeof(pm_node_t *) * list->capacity); *list = (pm_node_list_t) { 0 }; } } @@ -132,6 +136,7 @@ pm_node_destroy(pm_parser_t *parser, pm_node_t *node) { <%- raise -%> <%- end -%> <%- end -%> + xfree_sized(node, sizeof(pm_<%= node.human %>_t)); break; } <%- end -%> @@ -140,7 +145,6 @@ pm_node_destroy(pm_parser_t *parser, pm_node_t *node) { assert(false && "unreachable"); break; } - xfree(node); } /** diff --git a/prism/util/pm_buffer.c b/prism/util/pm_buffer.c index 2136a7c43eefd5..9e392427c6bae5 100644 --- a/prism/util/pm_buffer.c +++ b/prism/util/pm_buffer.c @@ -50,6 +50,7 @@ pm_buffer_length(const pm_buffer_t *buffer) { static inline bool pm_buffer_append_length(pm_buffer_t *buffer, size_t length) { size_t next_length = buffer->length + length; + const size_t original_capacity = buffer->capacity; if (next_length > buffer->capacity) { if (buffer->capacity == 0) { @@ -60,7 +61,7 @@ pm_buffer_append_length(pm_buffer_t *buffer, size_t length) { buffer->capacity *= 2; } - buffer->value = xrealloc(buffer->value, buffer->capacity); + buffer->value = xrealloc_sized(buffer->value, buffer->capacity, original_capacity); if (buffer->value == NULL) return false; } @@ -353,5 +354,5 @@ pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size_t le */ void pm_buffer_free(pm_buffer_t *buffer) { - xfree(buffer->value); + xfree_sized(buffer->value, buffer->capacity); } diff --git a/prism/util/pm_constant_pool.c b/prism/util/pm_constant_pool.c index 922ce6a18cfaff..bde7f959ea4318 100644 --- a/prism/util/pm_constant_pool.c +++ b/prism/util/pm_constant_pool.c @@ -33,8 +33,13 @@ pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity) bool pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id) { if (list->size >= list->capacity) { + const size_t original_capacity = list->capacity; list->capacity = list->capacity == 0 ? 8 : list->capacity * 2; - list->ids = (pm_constant_id_t *) xrealloc(list->ids, sizeof(pm_constant_id_t) * list->capacity); + list->ids = (pm_constant_id_t *) xrealloc_sized( + list->ids, + sizeof(pm_constant_id_t) * list->capacity, + sizeof(pm_constant_id_t) * original_capacity + ); if (list->ids == NULL) return false; } @@ -71,7 +76,7 @@ pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id) { void pm_constant_id_list_free(pm_constant_id_list_t *list) { if (list->ids != NULL) { - xfree(list->ids); + xfree_sized(list->ids, list->capacity * sizeof(pm_constant_id_t)); } } @@ -165,7 +170,7 @@ pm_constant_pool_resize(pm_constant_pool_t *pool) { // pool->constants and pool->buckets are allocated out of the same chunk // of memory, with the buckets coming first. - xfree(pool->buckets); + xfree_sized(pool->buckets, pool->capacity * element_size); pool->constants = next_constants; pool->buckets = next_buckets; pool->capacity = next_capacity; @@ -257,12 +262,12 @@ pm_constant_pool_insert(pm_constant_pool_t *pool, const uint8_t *start, size_t l // an existing constant, then either way we don't want the given // memory. Either it's duplicated with the existing constant or // it's not necessary because we have a shared version. - xfree((void *) start); + xfree_sized((void *) start, length); } else if (bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED) { // If we're attempting to insert a shared constant and the // existing constant is owned, then we can free the owned // constant and replace it with the shared constant. - xfree((void *) constant->start); + xfree_sized((void *) constant->start, constant->length); constant->start = start; bucket->type = (unsigned int) (type & 0x3); } @@ -334,9 +339,9 @@ pm_constant_pool_free(pm_constant_pool_t *pool) { // If an id is set on this constant, then we know we have content here. if (bucket->id != PM_CONSTANT_ID_UNSET && bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED) { pm_constant_t *constant = &pool->constants[bucket->id - 1]; - xfree((void *) constant->start); + xfree_sized((void *) constant->start, constant->length); } } - xfree(pool->buckets); + xfree_sized(pool->buckets, pool->capacity * (sizeof(pm_constant_pool_bucket_t) + sizeof(pm_constant_t))); } diff --git a/prism/util/pm_integer.c b/prism/util/pm_integer.c index 4170ecc58db81b..2b77a4b5d2f9d4 100644 --- a/prism/util/pm_integer.c +++ b/prism/util/pm_integer.c @@ -374,7 +374,7 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u } } - xfree(bigints); + xfree_sized(bigints, bigints_length * sizeof(pm_integer_t)); bigints = next_bigints; bigints_length = next_length; } @@ -383,7 +383,7 @@ pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, u destination->negative = source->negative; pm_integer_normalize(destination); - xfree(bigints); + xfree_sized(bigints, bigints_length * sizeof(pm_integer_t)); pm_integer_free(&base); } @@ -422,7 +422,7 @@ pm_integer_parse_powof2(pm_integer_t *integer, uint32_t base, const uint8_t *dig static void pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t digits_length) { const size_t batch = 9; - size_t length = (digits_length + batch - 1) / batch; + const size_t length = (digits_length + batch - 1) / batch; uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t)); uint32_t value = 0; @@ -439,7 +439,7 @@ pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t di // Convert base from 10**9 to 1<<32. pm_integer_convert_base(integer, &((pm_integer_t) { .length = length, .values = values, .value = 0, .negative = false }), 1000000000, ((uint64_t) 1 << 32)); - xfree(values); + xfree_sized(values, length * sizeof(uint32_t)); } /** @@ -448,7 +448,8 @@ pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t di static void pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t *start, const uint8_t *end) { // Allocate an array to store digits. - uint8_t *digits = xmalloc(sizeof(uint8_t) * (size_t) (end - start)); + const size_t digits_capa = sizeof(uint8_t) * (size_t) (end - start); + uint8_t *digits = xmalloc(digits_capa); size_t digits_length = 0; for (; start < end; start++) { @@ -463,7 +464,7 @@ pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t * pm_integer_parse_powof2(integer, multiplier, digits, digits_length); } - xfree(digits); + xfree_sized(digits, digits_capa); } /** @@ -635,7 +636,7 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) { } // Allocate a buffer that we'll copy the decimal digits into. - size_t digits_length = converted.length * 9; + const size_t digits_length = converted.length * 9; char *digits = xcalloc(digits_length, sizeof(char)); if (digits == NULL) return; @@ -654,7 +655,7 @@ pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) { // Finally, append the string to the buffer and free the digits. pm_buffer_append_string(buffer, digits + start_offset, digits_length - start_offset); - xfree(digits); + xfree_sized(digits, sizeof(char) * digits_length); pm_integer_free(&converted); } diff --git a/prism/util/pm_line_offset_list.c b/prism/util/pm_line_offset_list.c index 710fa4b788da44..d55b2f6874d76c 100644 --- a/prism/util/pm_line_offset_list.c +++ b/prism/util/pm_line_offset_list.c @@ -39,7 +39,7 @@ pm_line_offset_list_append(pm_line_offset_list_t *list, uint32_t cursor) { if (list->offsets == NULL) return false; memcpy(list->offsets, original_offsets, list->size * sizeof(uint32_t)); - xfree(original_offsets); + xfree_sized(original_offsets, list->size * sizeof(uint32_t)); } assert(list->size == 0 || cursor > list->offsets[list->size - 1]); @@ -109,5 +109,5 @@ pm_line_offset_list_line_column(const pm_line_offset_list_t *list, uint32_t curs */ void pm_line_offset_list_free(pm_line_offset_list_t *list) { - xfree(list->offsets); + xfree_sized(list->offsets, list->capacity * sizeof(uint32_t)); } diff --git a/prism/util/pm_list.c b/prism/util/pm_list.c index ad2294cd603f4b..940baffb64da35 100644 --- a/prism/util/pm_list.c +++ b/prism/util/pm_list.c @@ -41,7 +41,7 @@ pm_list_free(pm_list_t *list) { while (node != NULL) { next = node->next; - xfree(node); + xfree_sized(node, sizeof(pm_list_node_t)); node = next; } diff --git a/prism/util/pm_string.c b/prism/util/pm_string.c index a7493c468b166e..5ba8c78ec17d9c 100644 --- a/prism/util/pm_string.c +++ b/prism/util/pm_string.c @@ -71,9 +71,10 @@ pm_string_file_handle_open(pm_string_file_handle_t *handle, const char *filepath int length = MultiByteToWideChar(CP_UTF8, 0, filepath, -1, NULL, 0); if (length == 0) return PM_STRING_INIT_ERROR_GENERIC; - handle->path = xmalloc(sizeof(WCHAR) * ((size_t) length)); + const size_t path_size = sizeof(WCHAR) * ((size_t) length); + handle->path = xmalloc(path_size); if ((handle->path == NULL) || (MultiByteToWideChar(CP_UTF8, 0, filepath, -1, handle->path, length) == 0)) { - xfree(handle->path); + xfree_sized(handle->path, path_size); return PM_STRING_INIT_ERROR_GENERIC; } @@ -88,7 +89,7 @@ pm_string_file_handle_open(pm_string_file_handle_t *handle, const char *filepath } } - xfree(handle->path); + xfree_sized(handle->path, path_size); return result; } @@ -215,7 +216,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) { if (result != PM_STRING_INIT_SUCCESS) return result; // Get the file size. - DWORD file_size = GetFileSize(handle.file, NULL); + const DWORD file_size = GetFileSize(handle.file, NULL); if (file_size == INVALID_FILE_SIZE) { pm_string_file_handle_close(&handle); return PM_STRING_INIT_ERROR_GENERIC; @@ -245,7 +246,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) { // Check the number of bytes read if (bytes_read != file_size) { - xfree(source); + xfree_sized(source, file_size); pm_string_file_handle_close(&handle); return PM_STRING_INIT_ERROR_GENERIC; } @@ -281,7 +282,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) { return PM_STRING_INIT_SUCCESS; } - size_t length = (size_t) size; + const size_t length = (size_t) size; uint8_t *source = xmalloc(length); if (source == NULL) { close(fd); @@ -292,7 +293,7 @@ pm_string_file_init(pm_string_t *string, const char *filepath) { close(fd); if (bytes_read == -1) { - xfree(source); + xfree_sized(source, length); return PM_STRING_INIT_ERROR_GENERIC; } From c1f70394c94ad3047fbf9cdce8325d7ef0097e90 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 19 Feb 2026 22:43:18 -0800 Subject: [PATCH 11/35] Remove rb_imemo_tmpbuf_set_ptr --- internal/imemo.h | 7 ------- process.c | 7 ++----- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/internal/imemo.h b/internal/imemo.h index ed569317461011..59327ab2e303b1 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -139,7 +139,6 @@ static inline int imemo_type_p(VALUE imemo, enum imemo_type imemo_type); static inline bool imemo_throw_data_p(VALUE imemo); static inline struct vm_ifunc *rb_vm_ifunc_proc_new(rb_block_call_func_t func, const void *data); static inline void *RB_IMEMO_TMPBUF_PTR(VALUE v); -static inline void *rb_imemo_tmpbuf_set_ptr(VALUE v, void *ptr); static inline void MEMO_V1_SET(struct MEMO *m, VALUE v); static inline void MEMO_V2_SET(struct MEMO *m, VALUE v); @@ -193,12 +192,6 @@ RB_IMEMO_TMPBUF_PTR(VALUE v) return p->ptr; } -static inline void * -rb_imemo_tmpbuf_set_ptr(VALUE v, void *ptr) -{ - return ((rb_imemo_tmpbuf_t *)v)->ptr = ptr; -} - static inline VALUE rb_imemo_tmpbuf_new_from_an_RString(VALUE str) { diff --git a/process.c b/process.c index 006611d525e68f..126e36ee8d0d2a 100644 --- a/process.c +++ b/process.c @@ -2726,9 +2726,7 @@ open_func(void *ptr) static void rb_execarg_allocate_dup2_tmpbuf(struct rb_execarg *eargp, long len) { - VALUE tmpbuf = rb_imemo_tmpbuf_new(); - rb_imemo_tmpbuf_set_ptr(tmpbuf, ruby_xmalloc(run_exec_dup2_tmpbuf_size(len))); - eargp->dup2_tmpbuf = tmpbuf; + rb_alloc_tmp_buffer(&eargp->dup2_tmpbuf, run_exec_dup2_tmpbuf_size(len)); } static VALUE @@ -3183,8 +3181,7 @@ run_exec_dup2(VALUE ary, VALUE tmpbuf, struct rb_execarg *sargp, char *errmsg, s long n, i; int ret; int extra_fd = -1; - struct rb_imemo_tmpbuf_struct *buf = (void *)tmpbuf; - struct run_exec_dup2_fd_pair *pairs = (void *)buf->ptr; + struct run_exec_dup2_fd_pair *pairs = RB_IMEMO_TMPBUF_PTR(tmpbuf); n = RARRAY_LEN(ary); From 7a36808d78dc1a1ef3719225eaa59802a933cbf5 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 19 Feb 2026 23:03:28 -0800 Subject: [PATCH 12/35] Use rb_alloc_tmp_buffer in new_from_an_RString --- internal/imemo.h | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/internal/imemo.h b/internal/imemo.h index 59327ab2e303b1..c5c0d005f1ffa8 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -195,21 +195,13 @@ RB_IMEMO_TMPBUF_PTR(VALUE v) static inline VALUE rb_imemo_tmpbuf_new_from_an_RString(VALUE str) { - const void *src; VALUE imemo; - rb_imemo_tmpbuf_t *tmpbuf; - void *dst; size_t len; StringValue(str); - /* create tmpbuf to keep the pointer before xmalloc */ - imemo = rb_imemo_tmpbuf_new(); - tmpbuf = (rb_imemo_tmpbuf_t *)imemo; len = RSTRING_LEN(str); - src = RSTRING_PTR(str); - dst = ruby_xmalloc(len); - memcpy(dst, src, len); - tmpbuf->ptr = dst; + rb_alloc_tmp_buffer(&imemo, len); + memcpy(RB_IMEMO_TMPBUF_PTR(imemo), RSTRING_PTR(str), len); return imemo; } From 4b4da98832a6eaa4e9e6384325ca2a0040b13961 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 19 Feb 2026 23:04:21 -0800 Subject: [PATCH 13/35] Avoid and simplify rb_alloc_tmp_buffer_with_count There's no reason to ever call this and the "count" value is ignored. Because this is part of the public API I left it as a stub, but it could likely be removed. --- imemo.c | 20 +++++++++----------- include/ruby/internal/memory.h | 3 +-- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/imemo.c b/imemo.c index b8132aca69ff5f..3300ec4b7c24f6 100644 --- a/imemo.c +++ b/imemo.c @@ -63,29 +63,27 @@ rb_imemo_tmpbuf_new(void) } void * -rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) +rb_alloc_tmp_buffer(volatile VALUE *store, long len) { + if (len < 0) { + rb_raise(rb_eArgError, "negative buffer size (or size too big)"); + } + /* Keep the order; allocate an empty imemo first then xmalloc, to * get rid of potential memory leak */ rb_imemo_tmpbuf_t *tmpbuf = (rb_imemo_tmpbuf_t *)rb_imemo_tmpbuf_new(); *store = (VALUE)tmpbuf; - void *ptr = ruby_xmalloc(size); + void *ptr = ruby_xmalloc(len); tmpbuf->ptr = ptr; - tmpbuf->size = size; + tmpbuf->size = len; return ptr; } void * -rb_alloc_tmp_buffer(volatile VALUE *store, long len) +rb_alloc_tmp_buffer_with_count(volatile VALUE *store, size_t size, size_t cnt) { - long cnt; - - if (len < 0 || (cnt = (long)roomof(len, sizeof(VALUE))) < 0) { - rb_raise(rb_eArgError, "negative buffer size (or size too big)"); - } - - return rb_alloc_tmp_buffer_with_count(store, len, cnt); + return rb_alloc_tmp_buffer(store, (long)size); } void diff --git a/include/ruby/internal/memory.h b/include/ruby/internal/memory.h index cd099f85db9b31..eb9c1d252580f7 100644 --- a/include/ruby/internal/memory.h +++ b/include/ruby/internal/memory.h @@ -741,8 +741,7 @@ static inline void * rb_alloc_tmp_buffer2(volatile VALUE *store, long count, size_t elsize) { const size_t total_size = rbimpl_size_mul_or_raise(RBIMPL_CAST((size_t)count), elsize); - const size_t cnt = (total_size + sizeof(VALUE) - 1) / sizeof(VALUE); - return rb_alloc_tmp_buffer_with_count(store, total_size, cnt); + return rb_alloc_tmp_buffer(store, (long)total_size); } RBIMPL_SYMBOL_EXPORT_BEGIN() From 56bdb93b024495bfd544c75a00eaf5d68b15be6c Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 24 Feb 2026 13:28:10 -0800 Subject: [PATCH 14/35] Remove man page date check This doesn't work because actions/checkout uses a fetch depth of 1, so every CI run acts as though man pages were made in the most recent commit. Additionally the VCS code uses committed date, so this check will fail if any PR is made that changes man pages, but then is merged the following day JST. --- .github/workflows/check_misc.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 0039fa69718a75..1be94a016e4126 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -82,11 +82,6 @@ jobs: fi continue-on-error: ${{ steps.now.outputs.mon < 12 }} - - name: Check if date in man pages is up-to-date - run: | - make V=1 GIT=git BASERUBY=ruby update-man-date - git diff --color --no-ext-diff --ignore-submodules --exit-code -- man - - name: Check if to generate documents id: rdoc run: | From 7e028b861d6faf868d0fcd6002509417bade9e14 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 24 Feb 2026 17:35:17 -0500 Subject: [PATCH 15/35] ZJIT: Pull out GetEP from IsBlockParamModified (#16234) We can hopefully de-duplicate this GetEP soon. --- zjit/src/codegen.rs | 5 ++--- zjit/src/hir.rs | 17 ++++++++------- zjit/src/hir/opt_tests.rs | 40 ++++++++++++++++++----------------- zjit/src/hir/tests.rs | 44 ++++++++++++++++++++------------------- 4 files changed, 56 insertions(+), 50 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0d7c70d5971dae..6a705b90dbdaff 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -571,7 +571,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))), Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)), &Insn::GetLocal { ep_offset, level, use_sp, .. } => gen_getlocal(asm, ep_offset, level, use_sp), - &Insn::IsBlockParamModified { level } => gen_is_block_param_modified(asm, level), + &Insn::IsBlockParamModified { ep } => gen_is_block_param_modified(asm, opnd!(ep)), &Insn::GetBlockParam { ep_offset, level, state } => gen_getblockparam(jit, asm, ep_offset, level, &function.frame_state(state)), &Insn::SetLocal { val, ep_offset, level } => no_output!(gen_setlocal(asm, opnd!(val), function.type_of(val), ep_offset, level)), Insn::GetConstant { klass, id, allow_nil, state } => gen_getconstant(jit, asm, opnd!(klass), *id, opnd!(allow_nil), &function.frame_state(*state)), @@ -766,8 +766,7 @@ fn gen_setlocal(asm: &mut Assembler, val: Opnd, val_type: Type, local_ep_offset: } /// Returns 1 (as CBool) when VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM is set; returns 0 otherwise. -fn gen_is_block_param_modified(asm: &mut Assembler, level: u32) -> Opnd { - let ep = gen_get_ep(asm, level); +fn gen_is_block_param_modified(asm: &mut Assembler, ep: Opnd) -> Opnd { let flags = asm.load(Opnd::mem(VALUE_BITS, ep, SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32))); asm.test(flags, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into()); asm.csel_nz(Opnd::Imm(1), Opnd::Imm(0)) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ae316d409b0287..c73b5e917a62cc 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -858,7 +858,7 @@ pub enum Insn { GetLocal { level: u32, ep_offset: u32, use_sp: bool, rest_param: bool }, /// Check whether VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM is set in the environment flags. /// Returns CBool (0/1). - IsBlockParamModified { level: u32 }, + IsBlockParamModified { ep: InsnId }, /// Get the block parameter as a Proc. GetBlockParam { level: u32, ep_offset: u32, state: InsnId }, /// Set a local variable in a higher scope or the heap @@ -1658,8 +1658,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, ")); write!(f, "GetLocal {name}l{level}, EP@{ep_offset}{}", if rest_param { ", *" } else { "" }) }, - &Insn::IsBlockParamModified { level } => { - write!(f, "IsBlockParamModified l{level}") + &Insn::IsBlockParamModified { ep } => { + write!(f, "IsBlockParamModified {ep}") }, &Insn::SetLocal { val, level, ep_offset } => { let name = get_local_var_name_for_printer(self.iseq, level, ep_offset).map_or(String::new(), |x| format!("{x}, ")); @@ -2225,7 +2225,6 @@ impl Function { | PutSpecialObject {..} | GetGlobal {..} | GetLocal {..} - | IsBlockParamModified {..} | SideExit {..} | EntryPoint {..} | LoadPC @@ -2278,6 +2277,7 @@ impl Function { &GuardGreaterEq { left, right, reason, state } => GuardGreaterEq { left: find!(left), right: find!(right), reason, state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, &IsBlockGiven { lep } => IsBlockGiven { lep: find!(lep) }, + &IsBlockParamModified { ep } => IsBlockParamModified { ep: find!(ep) }, &GetBlockParam { level, ep_offset, state } => GetBlockParam { level, ep_offset, state: find!(state) }, &FixnumAdd { left, right, state } => FixnumAdd { left: find!(left), right: find!(right), state }, &FixnumSub { left, right, state } => FixnumSub { left: find!(left), right: find!(right), state }, @@ -4686,7 +4686,6 @@ impl Function { | &Insn::GetLEP | &Insn::LoadSelf | &Insn::GetLocal { .. } - | &Insn::IsBlockParamModified { .. } | &Insn::PutSpecialObject { .. } | &Insn::IncrCounter(_) | &Insn::IncrCounterPtr { .. } => @@ -4694,6 +4693,9 @@ impl Function { | &Insn::IsBlockGiven { lep } => { worklist.push_back(lep); } + &Insn::IsBlockParamModified { ep } => { + worklist.push_back(ep); + } &Insn::PatchPoint { state, .. } | &Insn::CheckInterrupts { state } | &Insn::GetBlockParam { state, .. } @@ -5544,6 +5546,7 @@ impl Function { | Insn::LoadField { .. } | Insn::GetConstantPath { .. } | Insn::IsBlockGiven { .. } + | Insn::IsBlockParamModified { .. } | Insn::GetGlobal { .. } | Insn::LoadPC | Insn::LoadEC @@ -5564,7 +5567,6 @@ impl Function { | Insn::GetSpecialSymbol { .. } | Insn::GetLocal { .. } | Insn::GetBlockParam { .. } - | Insn::IsBlockParamModified { .. } | Insn::StoreField { .. } => { Ok(()) } @@ -6995,7 +6997,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { // If the block param is already a Proc (modified), read it from EP. // Otherwise, convert it to a Proc and store it to EP. - let is_modified = fun.push_insn(block, Insn::IsBlockParamModified { level }); + let ep = fun.push_insn(block, Insn::GetEP { level }); + let is_modified = fun.push_insn(block, Insn::IsBlockParamModified { ep }); let locals_count = state.locals.len(); let stack_count = state.stack.len(); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 94c70d29031e94..2dcbf2ee97cc14 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -4476,16 +4476,17 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :block@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - v13:CBool = IsBlockParamModified l0 - IfTrue v13, bb4(v8, v9) - v24:BasicObject = GetBlockParam :block, l0, EP@3 - Jump bb6(v8, v24, v24) - bb4(v14:BasicObject, v15:BasicObject): - v22:BasicObject = GetLocal :block, l0, EP@3 - Jump bb6(v14, v22, v22) - bb6(v26:BasicObject, v27:BasicObject, v28:BasicObject): + v13:CPtr = GetEP 0 + v14:CBool = IsBlockParamModified v13 + IfTrue v14, bb4(v8, v9) + v25:BasicObject = GetBlockParam :block, l0, EP@3 + Jump bb6(v8, v25, v25) + bb4(v15:BasicObject, v16:BasicObject): + v23:BasicObject = GetLocal :block, l0, EP@3 + Jump bb6(v15, v23, v23) + bb6(v27:BasicObject, v28:BasicObject, v29:BasicObject): CheckInterrupts - Return v28 + Return v29 "); } @@ -4509,17 +4510,18 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v10:CBool = IsBlockParamModified l1 - IfTrue v10, bb4(v6) - v20:BasicObject = GetBlockParam :block, l1, EP@3 - Jump bb6(v6, v20) - bb4(v11:BasicObject): - v17:CPtr = GetEP 1 - v18:BasicObject = LoadField v17, :block@0x1000 - Jump bb6(v11, v18) - bb6(v22:BasicObject, v23:BasicObject): + v10:CPtr = GetEP 1 + v11:CBool = IsBlockParamModified v10 + IfTrue v11, bb4(v6) + v21:BasicObject = GetBlockParam :block, l1, EP@3 + Jump bb6(v6, v21) + bb4(v12:BasicObject): + v18:CPtr = GetEP 1 + v19:BasicObject = LoadField v18, :block@0x1000 + Jump bb6(v12, v19) + bb6(v23:BasicObject, v24:BasicObject): CheckInterrupts - Return v23 + Return v24 "); } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index bcfecaa38867b0..aa71daa0e80567 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3089,18 +3089,19 @@ pub mod hir_build_tests { v6:BasicObject = LoadArg :block@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - v13:CBool = IsBlockParamModified l0 - IfTrue v13, bb4(v8, v9) + v13:CPtr = GetEP 0 + v14:CBool = IsBlockParamModified v13 + IfTrue v14, bb4(v8, v9) Jump bb5(v8, v9) - bb4(v14:BasicObject, v15:BasicObject): - v22:BasicObject = GetLocal :block, l0, EP@3 - Jump bb6(v14, v22, v22) - bb5(v17:BasicObject, v18:BasicObject): - v24:BasicObject = GetBlockParam :block, l0, EP@3 - Jump bb6(v17, v24, v24) - bb6(v26:BasicObject, v27:BasicObject, v28:BasicObject): + bb4(v15:BasicObject, v16:BasicObject): + v23:BasicObject = GetLocal :block, l0, EP@3 + Jump bb6(v15, v23, v23) + bb5(v18:BasicObject, v19:BasicObject): + v25:BasicObject = GetBlockParam :block, l0, EP@3 + Jump bb6(v18, v25, v25) + bb6(v27:BasicObject, v28:BasicObject, v29:BasicObject): CheckInterrupts - Return v28 + Return v29 "); } @@ -3124,19 +3125,20 @@ pub mod hir_build_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - v10:CBool = IsBlockParamModified l1 - IfTrue v10, bb4(v6) + v10:CPtr = GetEP 1 + v11:CBool = IsBlockParamModified v10 + IfTrue v11, bb4(v6) Jump bb5(v6) - bb4(v11:BasicObject): - v17:CPtr = GetEP 1 - v18:BasicObject = LoadField v17, :block@0x1000 - Jump bb6(v11, v18) - bb5(v13:BasicObject): - v20:BasicObject = GetBlockParam :block, l1, EP@3 - Jump bb6(v13, v20) - bb6(v22:BasicObject, v23:BasicObject): + bb4(v12:BasicObject): + v18:CPtr = GetEP 1 + v19:BasicObject = LoadField v18, :block@0x1000 + Jump bb6(v12, v19) + bb5(v14:BasicObject): + v21:BasicObject = GetBlockParam :block, l1, EP@3 + Jump bb6(v14, v21) + bb6(v23:BasicObject, v24:BasicObject): CheckInterrupts - Return v23 + Return v24 "); } From 7da57563c8dddadba824734c70284d1f221d1f2c Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 24 Feb 2026 15:36:20 -0700 Subject: [PATCH 16/35] ZJIT: Refine effects for IsBitNotEqual and FixnumBitCheck to Empty (#16237) Both instructions are pure computations with no side effects: IsBitNotEqual is a cmp+csel (identical to IsBitEqual, already Empty), and FixnumBitCheck is a test+csel on a tagged Fixnum bit. Neither reads memory, writes anything, allocates, or can raise exceptions. --- zjit/src/hir.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index c73b5e917a62cc..5e385bd1f043c3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1149,7 +1149,7 @@ impl Insn { Insn::IsNil { .. } => effects::Empty, Insn::IsMethodCfunc { .. } => effects::Any, Insn::IsBitEqual { .. } => effects::Empty, - Insn::IsBitNotEqual { .. } => effects::Any, + Insn::IsBitNotEqual { .. } => effects::Empty, Insn::BoxBool { .. } => effects::Empty, Insn::BoxFixnum { .. } => effects::Empty, Insn::UnboxFixnum { .. } => effects::Any, @@ -1158,7 +1158,7 @@ impl Insn { Insn::GetConstant { .. } => effects::Any, Insn::GetConstantPath { .. } => effects::Any, Insn::IsBlockGiven { .. } => Effect::read_write(abstract_heaps::Other, abstract_heaps::Empty), - Insn::FixnumBitCheck { .. } => effects::Any, + Insn::FixnumBitCheck { .. } => effects::Empty, Insn::IsA { .. } => effects::Empty, Insn::GetGlobal { .. } => effects::Any, Insn::SetGlobal { .. } => effects::Any, From f1c2fafa0c4394f15927cc9238de970e6970a42d Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Tue, 24 Feb 2026 17:43:33 -0500 Subject: [PATCH 17/35] ZJIT: Ratchet down Guard* effects (#16236) Guards don't write to (e.g. PatchPoint) memory, just change if we side-exit into the interpreter or not, so lower their effects. --- zjit/src/hir.rs | 15 +++++++-------- zjit/src/hir/opt_tests.rs | 14 -------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5e385bd1f043c3..eb4dc985509b7e 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1230,13 +1230,13 @@ impl Insn { Insn::FixnumRShift { .. } => effects::Empty, Insn::ObjToString { .. } => effects::Any, Insn::AnyToString { .. } => effects::Any, - Insn::GuardType { .. } => effects::Any, - Insn::GuardTypeNot { .. } => effects::Any, - Insn::GuardBitEquals { .. } => effects::Any, - Insn::GuardAnyBitSet { .. } => effects::Any, - Insn::GuardNoBitsSet { .. } => effects::Any, - Insn::GuardGreaterEq { .. } => effects::Any, - Insn::GuardLess { .. } => effects::Any, + Insn::GuardType { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control), + Insn::GuardTypeNot { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control), + Insn::GuardBitEquals { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control), + Insn::GuardAnyBitSet { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control), + Insn::GuardNoBitsSet { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control), + Insn::GuardGreaterEq { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control), + Insn::GuardLess { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Control), Insn::PatchPoint { .. } => Effect::read_write(abstract_heaps::PatchPoint, abstract_heaps::Control), Insn::SideExit { .. } => effects::Any, Insn::IncrCounter(_) => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Other), @@ -8507,7 +8507,6 @@ mod graphviz_tests { v28:Fixnum = GuardType v12, Fixnum  v29:Fixnum = FixnumOr v27, v28  IncrCounter inline_cfunc_optimized_send_count  - PatchPoint NoTracePoint  CheckInterrupts  Return v29  >]; diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 2dcbf2ee97cc14..5c14ad06001365 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -235,7 +235,6 @@ mod hir_opt_tests { v46:Fixnum[0] = Const Value(0) IncrCounter inline_cfunc_optimized_send_count v20:Fixnum[0] = Const Value(0) - PatchPoint MethodRedefined(Integer@0x1000, *@0x1008, cme:0x1010) v39:Fixnum = GuardType v9, Fixnum v47:Fixnum[0] = Const Value(0) IncrCounter inline_cfunc_optimized_send_count @@ -6091,8 +6090,6 @@ mod hir_opt_tests { v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count v31:StaticSymbol[:b] = Const Value(VALUE(0x1038)) - PatchPoint SingleRactorMode - PatchPoint NoSingletonClass(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, one@0x1040, cme:0x1048) v28:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -8649,7 +8646,6 @@ mod hir_opt_tests { v20:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v19, Value(VALUE(0x1040)) v21:RubyValue = LoadField v18, :_ep_specval@0x1048 v22:FalseClass = GuardBitEquals v21, Value(false) - PatchPoint MethodRedefined(Array@0x1000, pop@0x1008, cme:0x1010) v28:CPtr = GetLEP v29:RubyValue = LoadField v28, :_ep_method_entry@0x1038 v30:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v29, Value(VALUE(0x1040)) @@ -8693,7 +8689,6 @@ mod hir_opt_tests { v24:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v23, Value(VALUE(0x1040)) v25:RubyValue = LoadField v22, :_ep_specval@0x1048 v26:FalseClass = GuardBitEquals v25, Value(false) - PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) v36:CPtr = GetLEP v37:RubyValue = LoadField v36, :_ep_method_entry@0x1038 v38:CallableMethodEntry[VALUE(0x1040)] = GuardBitEquals v37, Value(VALUE(0x1040)) @@ -9916,7 +9911,6 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) v29:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -9951,7 +9945,6 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) v31:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count @@ -9987,7 +9980,6 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) v29:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count @@ -10024,7 +10016,6 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) v31:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count @@ -10061,7 +10052,6 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) v31:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count @@ -10098,7 +10088,6 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) v31:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -10134,7 +10123,6 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) v31:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -10170,7 +10158,6 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) v31:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count @@ -10205,7 +10192,6 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) - PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1070, cme:0x1078) v31:FalseClass = Const Value(false) IncrCounter inline_cfunc_optimized_send_count From c5d85d5ab0c317ecbf2054e98ce203ffdc148e4f Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Sat, 21 Feb 2026 21:32:51 -0800 Subject: [PATCH 18/35] Improve error message failing to copy proc Previously, when passing an unshareable proc to a Ractor we would get the message: 'Ractor.new': allocator undefined for Proc (TypeError) With this change we get: 'Ractor.new': can not copy unshareable object # (Ractor::Error) Which should hopefully be a little clearer about the problem and make it simpler to debug. --- bootstraptest/test_ractor.rb | 6 +++--- ractor.c | 3 +++ test/ruby/test_ractor.rb | 10 ++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 9242dce7dcb6b4..08f9b73c13192b 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -507,14 +507,14 @@ def test n } # To copy the object, now Marshal#dump is used -assert_equal "allocator undefined for Thread", %q{ +assert_match /can not copy unshareable object/, %q{ obj = Thread.new{} begin r = Ractor.new obj do |msg| msg end - rescue TypeError => e - e.message #=> no _dump_data is defined for class Thread + rescue Ractor::Error => e + e.message else 'ng' end diff --git a/ractor.c b/ractor.c index df8d46b9fe578d..a6b017ffe3348f 100644 --- a/ractor.c +++ b/ractor.c @@ -2075,6 +2075,9 @@ copy_enter(VALUE obj, struct obj_traverse_replace_data *data) return traverse_skip; } else { + if (!rb_get_alloc_func(rb_obj_class(obj))) { + rb_raise(rb_eRactorError, "can not copy unshareable object %+"PRIsVALUE, obj); + } data->replacement = rb_obj_clone(obj); return traverse_cont; } diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index eaf861946ef8f1..4aeda424598f13 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -213,6 +213,16 @@ def test_ifunc_proc_not_shareable assert_unshareable(pr, /not supported yet/, exception: RuntimeError) end + def test_copy_unshareable_object_error_message + assert_ractor(<<~'RUBY') + pr = proc {} + err = assert_raise(Ractor::Error) do + Ractor.new(pr) {}.join + end + assert_match(/can not copy unshareable object/, err.message) + RUBY + end + def test_ractor_new_raises_isolation_error_if_outer_variables_are_accessed assert_raise(Ractor::IsolationError) do channel = Ractor::Port.new From da1daba07afccccf89226c5f8b24e83a6b051371 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Tue, 24 Feb 2026 17:21:14 -0700 Subject: [PATCH 19/35] ZJIT: Refine effects for CheckInterrupts to InterruptFlag|Control (#16240) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CheckInterrupts only reads ec->interrupt_flag and conditionally side-exits — it doesn't touch PatchPoint invariants or the allocator. Add an InterruptFlag leaf under Memory in the abstract heap DAG and narrow CheckInterrupts from Any to read(InterruptFlag)|write(Control). This lets remove_redundant_patch_points see through CheckInterrupts, eliminating duplicate PatchPoints that were previously separated by an interrupt check. --- zjit/src/hir.rs | 2 +- zjit/src/hir/opt_tests.rs | 6 ------ zjit/src/hir_effect/gen_hir_effect.rb | 1 + zjit/src/hir_effect/hir_effect.inc.rs | 20 ++++++++++++-------- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index eb4dc985509b7e..a1fc0e90cff8b2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1241,7 +1241,7 @@ impl Insn { Insn::SideExit { .. } => effects::Any, Insn::IncrCounter(_) => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Other), Insn::IncrCounterPtr { .. } => Effect::read_write(abstract_heaps::Empty, abstract_heaps::Other), - Insn::CheckInterrupts { .. } => effects::Any, + Insn::CheckInterrupts { .. } => Effect::read_write(abstract_heaps::InterruptFlag, abstract_heaps::Control), Insn::InvokeProc { .. } => effects::Any, Insn::RefineType { .. } => effects::Empty, Insn::HasType { .. } => effects::Empty, diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 5c14ad06001365..202d92b833e0d4 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -623,9 +623,6 @@ mod hir_opt_tests { v60:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - v23:Fixnum[2] = Const Value(2) - v25:Fixnum[2] = Const Value(2) - PatchPoint MethodRedefined(Integer@0x1000, <=@0x1008, cme:0x1010) v62:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -697,9 +694,6 @@ mod hir_opt_tests { v60:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts - v23:Fixnum[2] = Const Value(2) - v25:Fixnum[2] = Const Value(2) - PatchPoint MethodRedefined(Integer@0x1000, >=@0x1008, cme:0x1010) v62:TrueClass = Const Value(true) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts diff --git a/zjit/src/hir_effect/gen_hir_effect.rb b/zjit/src/hir_effect/gen_hir_effect.rb index cd4ca73c103a03..1cc552d1e6ea7d 100644 --- a/zjit/src/hir_effect/gen_hir_effect.rb +++ b/zjit/src/hir_effect/gen_hir_effect.rb @@ -49,6 +49,7 @@ def to_graphviz effect, f control = any.subeffect 'Control' memory = any.subeffect 'Memory' patch_point = any.subeffect 'PatchPoint' +interrupt_flag = memory.subeffect 'InterruptFlag' other = memory.subeffect 'Other' frame = memory.subeffect 'Frame' pc = frame.subeffect 'PC' diff --git a/zjit/src/hir_effect/hir_effect.inc.rs b/zjit/src/hir_effect/hir_effect.inc.rs index 7c5fc53ab3bb46..75e3447da1f217 100644 --- a/zjit/src/hir_effect/hir_effect.inc.rs +++ b/zjit/src/hir_effect/hir_effect.inc.rs @@ -5,13 +5,14 @@ mod bits { pub const Control: u8 = 1u8 << 1; pub const Empty: u8 = 0u8; pub const Frame: u8 = Locals | PC | Stack; - pub const Locals: u8 = 1u8 << 2; - pub const Memory: u8 = Frame | Other; - pub const Other: u8 = 1u8 << 3; - pub const PC: u8 = 1u8 << 4; - pub const PatchPoint: u8 = 1u8 << 5; - pub const Stack: u8 = 1u8 << 6; - pub const AllBitPatterns: [(&str, u8); 11] = [ + pub const InterruptFlag: u8 = 1u8 << 2; + pub const Locals: u8 = 1u8 << 3; + pub const Memory: u8 = Frame | InterruptFlag | Other; + pub const Other: u8 = 1u8 << 4; + pub const PC: u8 = 1u8 << 5; + pub const PatchPoint: u8 = 1u8 << 6; + pub const Stack: u8 = 1u8 << 7; + pub const AllBitPatterns: [(&str, u8); 12] = [ ("Any", Any), ("Memory", Memory), ("Frame", Frame), @@ -20,11 +21,12 @@ mod bits { ("PC", PC), ("Other", Other), ("Locals", Locals), + ("InterruptFlag", InterruptFlag), ("Control", Control), ("Allocator", Allocator), ("Empty", Empty), ]; - pub const NumEffectBits: u8 = 7; + pub const NumEffectBits: u8 = 8; } pub mod effect_types { pub type EffectBits = u8; @@ -36,6 +38,7 @@ pub mod abstract_heaps { pub const Control: AbstractHeap = AbstractHeap::from_bits(bits::Control); pub const Empty: AbstractHeap = AbstractHeap::from_bits(bits::Empty); pub const Frame: AbstractHeap = AbstractHeap::from_bits(bits::Frame); + pub const InterruptFlag: AbstractHeap = AbstractHeap::from_bits(bits::InterruptFlag); pub const Locals: AbstractHeap = AbstractHeap::from_bits(bits::Locals); pub const Memory: AbstractHeap = AbstractHeap::from_bits(bits::Memory); pub const Other: AbstractHeap = AbstractHeap::from_bits(bits::Other); @@ -50,6 +53,7 @@ pub mod effects { pub const Control: Effect = Effect::promote(abstract_heaps::Control); pub const Empty: Effect = Effect::promote(abstract_heaps::Empty); pub const Frame: Effect = Effect::promote(abstract_heaps::Frame); + pub const InterruptFlag: Effect = Effect::promote(abstract_heaps::InterruptFlag); pub const Locals: Effect = Effect::promote(abstract_heaps::Locals); pub const Memory: Effect = Effect::promote(abstract_heaps::Memory); pub const Other: Effect = Effect::promote(abstract_heaps::Other); From 9aa3dc6f82c6f8885b90f078f9537104ee6a0bc9 Mon Sep 17 00:00:00 2001 From: Scott Myron Date: Tue, 24 Feb 2026 21:53:15 -0600 Subject: [PATCH 20/35] [ruby/json] Remove unused load_uint8x16_4 function. https://github.com/ruby/json/commit/93bc1b3838 --- ext/json/simd/simd.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index 3bb86acdece37b..611b41b0668420 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -133,16 +133,6 @@ ALWAYS_INLINE(static) int string_scan_simd_neon(const char **ptr, const char *en return 0; } -static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table) -{ - uint8x16x4_t tab; - tab.val[0] = vld1q_u8(table); - tab.val[1] = vld1q_u8(table+16); - tab.val[2] = vld1q_u8(table+32); - tab.val[3] = vld1q_u8(table+48); - return tab; -} - #endif /* ARM Neon Support.*/ #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64) From fb39d5898f8b4ea0d7a4cea596d91cdf0dc81d8b Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Tue, 17 Feb 2026 10:27:06 -0500 Subject: [PATCH 21/35] [ruby/prism] rbs-inline https://github.com/ruby/prism/commit/1fdd4c5a49 --- lib/prism.rb | 33 ++ lib/prism/desugar_compiler.rb | 78 ++++- lib/prism/lex_compat.rb | 143 +++++--- lib/prism/node_ext.rb | 165 +++++++++- lib/prism/parse_result.rb | 311 ++++++++++++++++-- lib/prism/parse_result/comments.rb | 42 ++- lib/prism/parse_result/errors.rb | 7 +- lib/prism/parse_result/newlines.rb | 58 +++- lib/prism/pattern.rb | 50 ++- lib/prism/prism.gemspec | 40 ++- lib/prism/relocation.rb | 185 ++++++++++- lib/prism/string_query.rb | 16 +- lib/prism/translation.rb | 1 + lib/prism/translation/parser_current.rb | 1 - prism/templates/lib/prism/compiler.rb.erb | 9 + prism/templates/lib/prism/dispatcher.rb.erb | 23 +- prism/templates/lib/prism/dot_visitor.rb.erb | 36 +- prism/templates/lib/prism/dsl.rb.erb | 24 ++ .../lib/prism/inspect_visitor.rb.erb | 23 +- .../lib/prism/mutation_compiler.rb.erb | 3 + prism/templates/lib/prism/node.rb.erb | 161 ++++++++- prism/templates/lib/prism/reflection.rb.erb | 12 +- prism/templates/lib/prism/serialize.rb.erb | 244 ++++++++++---- prism/templates/lib/prism/visitor.rb.erb | 17 + prism/templates/template.rb | 31 +- 25 files changed, 1450 insertions(+), 263 deletions(-) diff --git a/lib/prism.rb b/lib/prism.rb index 5b3ce8752c267d..c7530e5874045d 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown # The Prism Ruby parser. @@ -37,6 +38,8 @@ module Prism # Raised when requested to parse as the currently running Ruby version but Prism has no support for it. class CurrentVersionError < ArgumentError # Initialize a new exception for the given ruby version string. + #-- + #: (String version) -> void def initialize(version) message = +"invalid version: Requested to parse as `version: 'current'`; " segments = @@ -61,6 +64,8 @@ def initialize(version) # resembles the return value of Ripper.lex. # # For supported options, see Prism.parse. + #-- + #: (String source, **untyped options) -> LexCompat::Result def self.lex_compat(source, **options) LexCompat.new(source, **options).result # steep:ignore end @@ -69,9 +74,37 @@ def self.lex_compat(source, **options) # load(source, serialized, freeze) -> ParseResult # # Load the serialized AST using the source as a reference into a tree. + #-- + #: (String source, String serialized, ?bool freeze) -> ParseResult def self.load(source, serialized, freeze = false) Serialize.load_parse(source, serialized, freeze) end + + # @rbs! + # VERSION: String + # BACKEND: :CEXT | :FFI + # + # interface _Stream + # def gets: (?Integer integer) -> (String | nil) + # end + # + # def self.parse: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult + # def self.profile: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> void + # def self.lex: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> LexResult + # def self.parse_lex: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseLexResult + # def self.dump: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> String + # def self.parse_comments: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> Array[Comment] + # def self.parse_success?: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool + # def self.parse_failure?: (String source, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool + # def self.parse_stream: (_Stream stream, ?filepath: String, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult + # def self.parse_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseResult + # def self.profile_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> void + # def self.lex_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> LexResult + # def self.parse_lex_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> ParseLexResult + # def self.dump_file: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> String + # def self.parse_file_comments: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> Array[Comment] + # def self.parse_file_success?: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool + # def self.parse_file_failure?: (String filepath, ?command_line: String, ?encoding: Encoding | false, ?freeze: bool, ?frozen_string_literal: bool, ?line: Integer, ?main_script: bool, ?partial_script: bool, ?scopes: Array[Array[Symbol]], ?version: String) -> bool end require_relative "prism/polyfill/byteindex" diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb index 7d4201c1c47b50..e08faa321919b0 100644 --- a/lib/prism/desugar_compiler.rb +++ b/lib/prism/desugar_compiler.rb @@ -1,12 +1,17 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism class DesugarAndWriteNode # :nodoc: include DSL - attr_reader :node, :default_source, :read_class, :write_class, :arguments + attr_reader :node #: ClassVariableAndWriteNode | ConstantAndWriteNode | GlobalVariableAndWriteNode | InstanceVariableAndWriteNode | LocalVariableAndWriteNode + attr_reader :default_source #: Source + attr_reader :read_class, :write_class #: Symbol + attr_reader :arguments #: Hash[Symbol, untyped] + #: ((ClassVariableAndWriteNode | ConstantAndWriteNode | GlobalVariableAndWriteNode | InstanceVariableAndWriteNode | LocalVariableAndWriteNode) node, Source default_source, Symbol read_class, Symbol write_class, **untyped arguments) -> void def initialize(node, default_source, read_class, write_class, **arguments) @node = node @default_source = default_source @@ -16,6 +21,8 @@ def initialize(node, default_source, read_class, write_class, **arguments) end # Desugar `x &&= y` to `x && x = y` + #-- + #: () -> node def compile and_node( location: node.location, @@ -36,8 +43,12 @@ def compile class DesugarOrWriteDefinedNode # :nodoc: include DSL - attr_reader :node, :default_source, :read_class, :write_class, :arguments + attr_reader :node #: ClassVariableOrWriteNode | ConstantOrWriteNode | GlobalVariableOrWriteNode + attr_reader :default_source #: Source + attr_reader :read_class, :write_class #: Symbol + attr_reader :arguments #: Hash[Symbol, untyped] + #: ((ClassVariableOrWriteNode | ConstantOrWriteNode | GlobalVariableOrWriteNode) node, Source default_source, Symbol read_class, Symbol write_class, **untyped arguments) -> void def initialize(node, default_source, read_class, write_class, **arguments) @node = node @default_source = default_source @@ -47,6 +58,8 @@ def initialize(node, default_source, read_class, write_class, **arguments) end # Desugar `x ||= y` to `defined?(x) ? x : x = y` + #-- + #: () -> node def compile if_node( location: node.location, @@ -87,8 +100,12 @@ def compile class DesugarOperatorWriteNode # :nodoc: include DSL - attr_reader :node, :default_source, :read_class, :write_class, :arguments + attr_reader :node #: ClassVariableOperatorWriteNode | ConstantOperatorWriteNode | GlobalVariableOperatorWriteNode | InstanceVariableOperatorWriteNode | LocalVariableOperatorWriteNode + attr_reader :default_source #: Source + attr_reader :read_class, :write_class #: Symbol + attr_reader :arguments #: Hash[Symbol, untyped] + #: ((ClassVariableOperatorWriteNode | ConstantOperatorWriteNode | GlobalVariableOperatorWriteNode | InstanceVariableOperatorWriteNode | LocalVariableOperatorWriteNode) node, Source default_source, Symbol read_class, Symbol write_class, **untyped arguments) -> void def initialize(node, default_source, read_class, write_class, **arguments) @node = node @default_source = default_source @@ -98,6 +115,8 @@ def initialize(node, default_source, read_class, write_class, **arguments) end # Desugar `x += y` to `x = x + y` + #-- + #: () -> node def compile binary_operator_loc = node.binary_operator_loc.chop @@ -131,8 +150,12 @@ def compile class DesugarOrWriteNode # :nodoc: include DSL - attr_reader :node, :default_source, :read_class, :write_class, :arguments + attr_reader :node #: InstanceVariableOrWriteNode | LocalVariableOrWriteNode + attr_reader :default_source #: Source + attr_reader :read_class, :write_class #: Symbol + attr_reader :arguments #: Hash[Symbol, untyped] + #: ((InstanceVariableOrWriteNode | LocalVariableOrWriteNode) node, Source default_source, Symbol read_class, Symbol write_class, **untyped arguments) -> void def initialize(node, default_source, read_class, write_class, **arguments) @node = node @default_source = default_source @@ -142,6 +165,8 @@ def initialize(node, default_source, read_class, write_class, **arguments) end # Desugar `x ||= y` to `x || x = y` + #-- + #: () -> node def compile or_node( location: node.location, @@ -162,90 +187,105 @@ def compile private_constant :DesugarAndWriteNode, :DesugarOrWriteNode, :DesugarOrWriteDefinedNode, :DesugarOperatorWriteNode class ClassVariableAndWriteNode + #: () -> node def desugar # :nodoc: DesugarAndWriteNode.new(self, source, :class_variable_read_node, :class_variable_write_node, name: name).compile end end class ClassVariableOrWriteNode + #: () -> node def desugar # :nodoc: DesugarOrWriteDefinedNode.new(self, source, :class_variable_read_node, :class_variable_write_node, name: name).compile end end class ClassVariableOperatorWriteNode + #: () -> node def desugar # :nodoc: DesugarOperatorWriteNode.new(self, source, :class_variable_read_node, :class_variable_write_node, name: name).compile end end class ConstantAndWriteNode + #: () -> node def desugar # :nodoc: DesugarAndWriteNode.new(self, source, :constant_read_node, :constant_write_node, name: name).compile end end class ConstantOrWriteNode + #: () -> node def desugar # :nodoc: DesugarOrWriteDefinedNode.new(self, source, :constant_read_node, :constant_write_node, name: name).compile end end class ConstantOperatorWriteNode + #: () -> node def desugar # :nodoc: DesugarOperatorWriteNode.new(self, source, :constant_read_node, :constant_write_node, name: name).compile end end class GlobalVariableAndWriteNode + #: () -> node def desugar # :nodoc: DesugarAndWriteNode.new(self, source, :global_variable_read_node, :global_variable_write_node, name: name).compile end end class GlobalVariableOrWriteNode + #: () -> node def desugar # :nodoc: DesugarOrWriteDefinedNode.new(self, source, :global_variable_read_node, :global_variable_write_node, name: name).compile end end class GlobalVariableOperatorWriteNode + #: () -> node def desugar # :nodoc: DesugarOperatorWriteNode.new(self, source, :global_variable_read_node, :global_variable_write_node, name: name).compile end end class InstanceVariableAndWriteNode + #: () -> node def desugar # :nodoc: DesugarAndWriteNode.new(self, source, :instance_variable_read_node, :instance_variable_write_node, name: name).compile end end class InstanceVariableOrWriteNode + #: () -> node def desugar # :nodoc: DesugarOrWriteNode.new(self, source, :instance_variable_read_node, :instance_variable_write_node, name: name).compile end end class InstanceVariableOperatorWriteNode + #: () -> node def desugar # :nodoc: DesugarOperatorWriteNode.new(self, source, :instance_variable_read_node, :instance_variable_write_node, name: name).compile end end class LocalVariableAndWriteNode + #: () -> node def desugar # :nodoc: DesugarAndWriteNode.new(self, source, :local_variable_read_node, :local_variable_write_node, name: name, depth: depth).compile end end class LocalVariableOrWriteNode + #: () -> node def desugar # :nodoc: DesugarOrWriteNode.new(self, source, :local_variable_read_node, :local_variable_write_node, name: name, depth: depth).compile end end class LocalVariableOperatorWriteNode + #: () -> node def desugar # :nodoc: DesugarOperatorWriteNode.new(self, source, :local_variable_read_node, :local_variable_write_node, name: name, depth: depth).compile end @@ -259,6 +299,8 @@ class DesugarCompiler < MutationCompiler # becomes # # `@@foo && @@foo = bar` + #-- + #: (ClassVariableAndWriteNode node) -> node def visit_class_variable_and_write_node(node) node.desugar end @@ -268,6 +310,8 @@ def visit_class_variable_and_write_node(node) # becomes # # `defined?(@@foo) ? @@foo : @@foo = bar` + #-- + #: (ClassVariableOrWriteNode node) -> node def visit_class_variable_or_write_node(node) node.desugar end @@ -277,6 +321,8 @@ def visit_class_variable_or_write_node(node) # becomes # # `@@foo = @@foo + bar` + #-- + #: (ClassVariableOperatorWriteNode node) -> node def visit_class_variable_operator_write_node(node) node.desugar end @@ -286,6 +332,8 @@ def visit_class_variable_operator_write_node(node) # becomes # # `Foo && Foo = bar` + #-- + #: (ConstantAndWriteNode node) -> node def visit_constant_and_write_node(node) node.desugar end @@ -295,6 +343,8 @@ def visit_constant_and_write_node(node) # becomes # # `defined?(Foo) ? Foo : Foo = bar` + #-- + #: (ConstantOrWriteNode node) -> node def visit_constant_or_write_node(node) node.desugar end @@ -304,6 +354,8 @@ def visit_constant_or_write_node(node) # becomes # # `Foo = Foo + bar` + #-- + #: (ConstantOperatorWriteNode node) -> node def visit_constant_operator_write_node(node) node.desugar end @@ -313,6 +365,8 @@ def visit_constant_operator_write_node(node) # becomes # # `$foo && $foo = bar` + #-- + #: (GlobalVariableAndWriteNode node) -> node def visit_global_variable_and_write_node(node) node.desugar end @@ -322,6 +376,8 @@ def visit_global_variable_and_write_node(node) # becomes # # `defined?($foo) ? $foo : $foo = bar` + #-- + #: (GlobalVariableOrWriteNode node) -> node def visit_global_variable_or_write_node(node) node.desugar end @@ -331,6 +387,8 @@ def visit_global_variable_or_write_node(node) # becomes # # `$foo = $foo + bar` + #-- + #: (GlobalVariableOperatorWriteNode node) -> node def visit_global_variable_operator_write_node(node) node.desugar end @@ -340,6 +398,8 @@ def visit_global_variable_operator_write_node(node) # becomes # # `@foo && @foo = bar` + #-- + #: (InstanceVariableAndWriteNode node) -> node def visit_instance_variable_and_write_node(node) node.desugar end @@ -349,6 +409,8 @@ def visit_instance_variable_and_write_node(node) # becomes # # `@foo || @foo = bar` + #-- + #: (InstanceVariableOrWriteNode node) -> node def visit_instance_variable_or_write_node(node) node.desugar end @@ -358,6 +420,8 @@ def visit_instance_variable_or_write_node(node) # becomes # # `@foo = @foo + bar` + #-- + #: (InstanceVariableOperatorWriteNode node) -> node def visit_instance_variable_operator_write_node(node) node.desugar end @@ -367,6 +431,8 @@ def visit_instance_variable_operator_write_node(node) # becomes # # `foo && foo = bar` + #-- + #: (LocalVariableAndWriteNode node) -> node def visit_local_variable_and_write_node(node) node.desugar end @@ -376,6 +442,8 @@ def visit_local_variable_and_write_node(node) # becomes # # `foo || foo = bar` + #-- + #: (LocalVariableOrWriteNode node) -> node def visit_local_variable_or_write_node(node) node.desugar end @@ -385,6 +453,8 @@ def visit_local_variable_or_write_node(node) # becomes # # `foo = foo + bar` + #-- + #: (LocalVariableOperatorWriteNode node) -> node def visit_local_variable_operator_write_node(node) node.desugar end diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 3d5cbfcddc0edd..bdef99acfc9535 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -1,25 +1,47 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism + # @rbs! + # module Translation + # class Ripper + # EXPR_BEG: Integer + # + # class Lexer < Ripper + # class State + # def self.[]: (Integer value) -> State + # end + # end + # end + # end + # This class is responsible for lexing the source using prism and then # converting those tokens to be compatible with Ripper. In the vast majority # of cases, this is a one-to-one mapping of the token type. Everything else # generally lines up. However, there are a few cases that require special # handling. class LexCompat # :nodoc: + # @rbs! + # # A token produced by the Ripper lexer that Prism is replicating. + # type lex_compat_token = [[Integer, Integer], Symbol, String, untyped] + # A result class specialized for holding tokens produced by the lexer. class Result < Prism::Result # The list of tokens that were produced by the lexer. - attr_reader :value + attr_reader :value #: Array[lex_compat_token] # Create a new lex compat result object with the given values. + #-- + #: (Array[lex_compat_token] value, Array[Comment] comments, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, Source source) -> void def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) @value = value super(comments, magic_comments, data_loc, errors, warnings, source) end # Implement the hash pattern matching interface for Result. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: super.merge!(value: value) end @@ -205,16 +227,19 @@ module Heredoc # :nodoc: # order back into the token stream and set the state of the last token to # the state that the heredoc was opened in. class PlainHeredoc # :nodoc: - attr_reader :tokens + attr_reader :tokens #: Array[lex_compat_token] + #: () -> void def initialize @tokens = [] end + #: (lex_compat_token token) -> void def <<(token) tokens << token end + #: () -> Array[lex_compat_token] def to_a tokens end @@ -224,21 +249,25 @@ def to_a # that need to be split on "\\\n" to mimic Ripper's behavior. We also need # to keep track of the state that the heredoc was opened in. class DashHeredoc # :nodoc: - attr_reader :split, :tokens + attr_reader :split #: bool + attr_reader :tokens #: Array[lex_compat_token] + #: (bool split) -> void def initialize(split) @split = split @tokens = [] end + #: (lex_compat_token token) -> void def <<(token) tokens << token end + #: () -> Array[lex_compat_token] def to_a embexpr_balance = 0 - tokens.each_with_object([]) do |token, results| #$ Array[Token] + tokens.each_with_object([]) do |token, results| #$ Array[lex_compat_token] case token[1] when :on_embexpr_beg embexpr_balance += 1 @@ -285,8 +314,13 @@ def to_a class DedentingHeredoc # :nodoc: TAB_WIDTH = 8 - attr_reader :tokens, :dedent_next, :dedent, :embexpr_balance + attr_reader :tokens #: Array[lex_compat_token] + attr_reader :dedent_next #: bool + attr_reader :dedent #: Integer? + attr_reader :embexpr_balance #: Integer + # @rbs @ended_on_newline: bool + #: () -> void def initialize @tokens = [] @dedent_next = true @@ -298,6 +332,8 @@ def initialize # As tokens are coming in, we track the minimum amount of common leading # whitespace on plain string content tokens. This allows us to later # remove that amount of whitespace from the beginning of each line. + # + #: (lex_compat_token token) -> void def <<(token) case token[1] when :on_embexpr_beg, :on_heredoc_beg @@ -310,7 +346,7 @@ def <<(token) line = token[2] if dedent_next && !(line.strip.empty? && line.end_with?("\n")) - leading = line[/\A(\s*)\n?/, 1] + leading = line[/\A(\s*)\n?/, 1] #: String next_dedent = 0 leading.each_char do |char| @@ -335,11 +371,12 @@ def <<(token) tokens << token end + #: () -> Array[lex_compat_token] def to_a # If every line in the heredoc is blank, we still need to split up the # string content token into multiple tokens. if dedent.nil? - results = [] #: Array[Token] + results = [] #: Array[lex_compat_token] embexpr_balance = 0 tokens.each do |token| @@ -374,7 +411,7 @@ def to_a # If the minimum common whitespace is 0, then we need to concatenate # string nodes together that are immediately adjacent. if dedent == 0 - results = [] #: Array[Token] + results = [] #: Array[lex_compat_token] embexpr_balance = 0 index = 0 @@ -407,7 +444,7 @@ def to_a # insert on_ignored_sp tokens for the amount of dedent that we need to # perform. We also need to remove the dedent from the beginning of # each line of plain string content tokens. - results = [] #: Array[Token] + results = [] #: Array[lex_compat_token] dedent_next = true embexpr_balance = 0 @@ -446,7 +483,8 @@ def to_a # line or this line doesn't start with whitespace, then we # should concatenate the rest of the string to match ripper. if dedent == 0 && (!dedent_next || !line.start_with?(/\s/)) - line = splits[index..].join + unjoined = splits[index..] #: Array[String] + line = unjoined.join index = splits.length end @@ -511,6 +549,8 @@ def to_a # Here we will split between the two types of heredocs and return the # object that will store their tokens. + #-- + #: (lex_compat_token opening) -> (PlainHeredoc | DashHeredoc | DedentingHeredoc) def self.build(opening) case opening[2][2] when "~" @@ -530,31 +570,38 @@ def self.build(opening) BOM_FLUSHED = RUBY_VERSION >= "3.3.0" private_constant :BOM_FLUSHED - attr_reader :options + attr_reader :options #: Hash[Symbol, untyped] + # @rbs @source: String - def initialize(code, **options) - @code = code + #: (String source, **untyped options) -> void + def initialize(source, **options) + @source = source @options = options end + #: () -> Result def result - tokens = [] #: Array[LexCompat::Token] + tokens = [] #: Array[lex_compat_token] state = :default heredoc_stack = [[]] #: Array[Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]] - result = Prism.lex(@code, **options) + result = Prism.lex(@source, **options) source = result.source result_value = result.value - previous_state = nil #: State? + previous_state = nil #: Translation::Ripper::Lexer::State? last_heredoc_end = nil #: Integer? - eof_token = nil + eof_token = nil #: Token? bom = source.slice(0, 3) == "\xEF\xBB\xBF" - result_value.each_with_index do |(token, lex_state), index| - lineno = token.location.start_line - column = token.location.start_column + result_value.each_with_index do |(prism_token, prism_state), index| + lineno = prism_token.location.start_line + column = prism_token.location.start_column + + event = RIPPER.fetch(prism_token.type) + value = prism_token.value + lex_state = Translation::Ripper::Lexer::State[prism_state] # If there's a UTF-8 byte-order mark as the start of the file, then for # certain tokens ripper sets the first token back by 3 bytes. It also @@ -566,43 +613,38 @@ def result if index == 0 && column == 0 && !BOM_FLUSHED flushed = - case token.type + case prism_token.type when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE, :GLOBAL_VARIABLE, :NUMBERED_REFERENCE, :PERCENT_LOWER_I, :PERCENT_LOWER_X, :PERCENT_LOWER_W, :PERCENT_UPPER_I, :PERCENT_UPPER_W, :STRING_BEGIN true when :REGEXP_BEGIN, :SYMBOL_BEGIN - token.value.start_with?("%") + value.start_with?("%") else false end unless flushed column -= 3 - value = token.value value.prepend(String.new("\xEF\xBB\xBF", encoding: value.encoding)) end end end - event = RIPPER.fetch(token.type) - value = token.value - lex_state = Translation::Ripper::Lexer::State[lex_state] - - token = + lex_compat_token = case event when :on___end__ # Ripper doesn't include the rest of the token in the event, so we need to # trim it down to just the content on the first line. - value = value[0..value.index("\n")] + value = value[0..value.index("\n")] #: String [[lineno, column], event, value, lex_state] when :on_comment [[lineno, column], event, value, lex_state] when :on_heredoc_end # Heredoc end tokens can be emitted in an odd order, so we don't # want to bother comparing the state on them. - last_heredoc_end = token.location.end_offset + last_heredoc_end = prism_token.location.end_offset [[lineno, column], event, value, lex_state] when :on_embexpr_end [[lineno, column], event, value, lex_state] @@ -615,7 +657,7 @@ def result end tokens << [[lineno, column], event, line, lex_state] end - tokens.pop + tokens.pop #: lex_compat_token when :on_regexp_end # On regex end, Ripper scans and then sets end state, so the ripper # lexed output is begin, when it should be end. prism sets lex state @@ -647,7 +689,7 @@ def result [[lineno, column], event, value, lex_state] when :on_eof - eof_token = token + eof_token = prism_token previous_token = result_value[index - 1][0] # If we're at the end of the file and the previous token was a @@ -662,7 +704,7 @@ def result # Use the greater offset of the two to determine the start of # the trailing whitespace. start_offset = [previous_token.location.end_offset, last_heredoc_end].compact.max - end_offset = token.location.start_offset + end_offset = prism_token.location.start_offset if start_offset < end_offset if bom @@ -677,7 +719,7 @@ def result [[lineno, column], event, value, lex_state] else [[lineno, column], event, value, lex_state] - end + end #: lex_compat_token previous_state = lex_state @@ -694,19 +736,19 @@ def result when :default # The default state is when there are no heredocs at all. In this # state we can append the token to the list of tokens and move on. - tokens << token + tokens << lex_compat_token # If we get the declaration of a heredoc, then we open a new heredoc # and move into the heredoc_opened state. if event == :on_heredoc_beg state = :heredoc_opened - heredoc_stack.last << Heredoc.build(token) + heredoc_stack.last << Heredoc.build(lex_compat_token) end when :heredoc_opened # The heredoc_opened state is when we've seen the declaration of a # heredoc and are now lexing the body of the heredoc. In this state we # push tokens onto the most recently created heredoc. - heredoc_stack.last.last << token + heredoc_stack.last.last << lex_compat_token case event when :on_heredoc_beg @@ -714,7 +756,7 @@ def result # heredoc, this means we have nested heredocs. In this case we'll # push a new heredoc onto the stack and stay in the heredoc_opened # state since we're now lexing the body of the new heredoc. - heredoc_stack << [Heredoc.build(token)] + heredoc_stack << [Heredoc.build(lex_compat_token)] when :on_heredoc_end # If we receive the end of a heredoc, then we're done lexing the # body of the heredoc. In this case we now have a completed heredoc @@ -723,10 +765,10 @@ def result state = :heredoc_closed end when :heredoc_closed - if %i[on_nl on_ignored_nl on_comment].include?(event) || (event == :on_tstring_content && value.end_with?("\n")) + if %i[on_nl on_ignored_nl on_comment].include?(event) || ((event == :on_tstring_content) && value.end_with?("\n")) if heredoc_stack.size > 1 - flushing = heredoc_stack.pop - heredoc_stack.last.last << token + flushing = heredoc_stack.pop #: Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc] + heredoc_stack.last.last << lex_compat_token flushing.each do |heredoc| heredoc.to_a.each do |flushed_token| @@ -738,12 +780,12 @@ def result next end elsif event == :on_heredoc_beg - tokens << token + tokens << lex_compat_token state = :heredoc_opened - heredoc_stack.last << Heredoc.build(token) + heredoc_stack.last << Heredoc.build(lex_compat_token) next elsif heredoc_stack.size > 1 - heredoc_stack[-2].last << token + heredoc_stack[-2].last << lex_compat_token next end @@ -754,13 +796,15 @@ def result heredoc_stack.last.clear state = :default - tokens << token + tokens << lex_compat_token end end # Drop the EOF token from the list. The EOF token may not be # present if the source was syntax invalid - tokens = tokens[0...-1] if tokens.dig(-1, 1) == :on_eof + if tokens.dig(-1, 1) == :on_eof + tokens = tokens[0...-1] #: Array[lex_compat_token] + end # We sort by location because Ripper.lex sorts. tokens.sort_by! do |token| @@ -775,8 +819,9 @@ def result private + #: (Array[lex_compat_token] tokens, Source source, Location? data_loc, bool bom, Token? eof_token) -> Array[lex_compat_token] def post_process_tokens(tokens, source, data_loc, bom, eof_token) - new_tokens = [] + new_tokens = [] #: Array[lex_compat_token] prev_token_state = Translation::Ripper::Lexer::State[Translation::Ripper::EXPR_BEG] prev_token_end = bom ? 3 : 0 @@ -806,8 +851,8 @@ def post_process_tokens(tokens, source, data_loc, bom, eof_token) next_whitespace_index = continuation_index + 1 next_whitespace_index += 1 if sp_value.byteslice(next_whitespace_index) == "\r" next_whitespace_index += 1 - first_whitespace = sp_value[0...continuation_index] - continuation = sp_value[continuation_index...next_whitespace_index] + first_whitespace = sp_value[0...continuation_index] #: String + continuation = sp_value[continuation_index...next_whitespace_index] #: String second_whitespace = sp_value[next_whitespace_index..] || "" new_tokens << [[sp_line, sp_column], :on_sp, first_whitespace, prev_token_state] unless first_whitespace.empty? diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index a05123d1bb3aa5..57593a16125cd7 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown #-- @@ -7,6 +8,7 @@ #++ module Prism class Node + #: (*String replacements) -> void def deprecated(*replacements) # :nodoc: location = caller_locations(1, 1) location = location[0].label if location @@ -23,7 +25,9 @@ def deprecated(*replacements) # :nodoc: module RegularExpressionOptions # :nodoc: # Returns a numeric value that represents the flags that were used to create # the regular expression. - def options + #-- + #: (Integer flags) -> Integer + def self.options(flags) o = 0 o |= Regexp::IGNORECASE if flags.anybits?(RegularExpressionFlags::IGNORE_CASE) o |= Regexp::EXTENDED if flags.anybits?(RegularExpressionFlags::EXTENDED) @@ -35,43 +39,87 @@ def options end class InterpolatedMatchLastLineNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end class InterpolatedRegularExpressionNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end class MatchLastLineNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end class RegularExpressionNode < Node - include RegularExpressionOptions + # Returns a numeric value that represents the flags that were used to create + # the regular expression. + #-- + #: () -> Integer + def options + RegularExpressionOptions.options(flags) + end end private_constant :RegularExpressionOptions module HeredocQuery # :nodoc: # Returns true if this node was represented as a heredoc in the source code. - def heredoc? + #-- + #: (String? opening) -> bool? + def self.heredoc?(opening) + # @type self: InterpolatedStringNode | InterpolatedXStringNode | StringNode | XStringNode opening&.start_with?("<<") end end class InterpolatedStringNode < Node - include HeredocQuery + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end end class InterpolatedXStringNode < Node - include HeredocQuery + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end end class StringNode < Node - include HeredocQuery + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end # Occasionally it's helpful to treat a string as if it were interpolated so # that there's a consistent interface for working with strings. + #-- + #: () -> InterpolatedStringNode def to_interpolated InterpolatedStringNode.new( source, @@ -86,10 +134,17 @@ def to_interpolated end class XStringNode < Node - include HeredocQuery + # Returns true if this node was represented as a heredoc in the source code. + #-- + #: () -> bool? + def heredoc? + HeredocQuery.heredoc?(opening) + end # Occasionally it's helpful to treat a string as if it were interpolated so # that there's a consistent interface for working with strings. + #-- + #: () -> InterpolatedXStringNode def to_interpolated InterpolatedXStringNode.new( source, @@ -107,6 +162,8 @@ def to_interpolated class ImaginaryNode < Node # Returns the value of the node as a Ruby Complex. + #-- + #: () -> Complex def value Complex(0, numeric.value) end @@ -114,12 +171,16 @@ def value class RationalNode < Node # Returns the value of the node as a Ruby Rational. + #-- + #: () -> Rational def value Rational(numerator, denominator) end # Returns the value of the node as an IntegerNode or a FloatNode. This # method is deprecated in favor of #value or #numerator/#denominator. + #-- + #: () -> (IntegerNode | FloatNode) def numeric deprecated("value", "numerator", "denominator") @@ -134,11 +195,15 @@ def numeric class ConstantReadNode < Node # Returns the list of parts for the full name of this constant. # For example: [:Foo] + #-- + #: () -> Array[Symbol] def full_name_parts [name] end # Returns the full name of this constant. For example: "Foo" + #-- + #: () -> String def full_name name.to_s end @@ -147,11 +212,15 @@ def full_name class ConstantWriteNode < Node # Returns the list of parts for the full name of this constant. # For example: [:Foo] + #-- + #: () -> Array[Symbol] def full_name_parts [name] end # Returns the full name of this constant. For example: "Foo" + #-- + #: () -> String def full_name name.to_s end @@ -173,6 +242,8 @@ class MissingNodesInConstantPathError < StandardError; end # Returns the list of parts for the full name of this constant path. # For example: [:Foo, :Bar] + #-- + #: () -> Array[Symbol] def full_name_parts parts = [] #: Array[Symbol] current = self #: node? @@ -195,6 +266,8 @@ def full_name_parts end # Returns the full name of this constant path. For example: "Foo::Bar" + #-- + #: () -> String def full_name full_name_parts.join("::") end @@ -202,10 +275,12 @@ def full_name # Previously, we had a child node on this class that contained either a # constant read or a missing node. To not cause a breaking change, we # continue to supply that API. + #-- + #: () -> (ConstantReadNode | MissingNode) def child deprecated("name", "name_loc") - if name + if (name = self.name) ConstantReadNode.new(source, -1, name_loc, 0, name) else MissingNode.new(source, -1, location, 0) @@ -216,9 +291,11 @@ def child class ConstantPathTargetNode < Node # Returns the list of parts for the full name of this constant path. # For example: [:Foo, :Bar] + #-- + #: () -> Array[Symbol] def full_name_parts parts = - case parent + case (parent = self.parent) when ConstantPathNode, ConstantReadNode parent.full_name_parts when nil @@ -228,7 +305,7 @@ def full_name_parts raise ConstantPathNode::DynamicPartsInConstantPathError, "Constant target path contains dynamic parts. Cannot compute full name" end - if name.nil? + if (name = self.name).nil? raise ConstantPathNode::MissingNodesInConstantPathError, "Constant target path contains missing nodes. Cannot compute full name" end @@ -236,6 +313,8 @@ def full_name_parts end # Returns the full name of this constant path. For example: "Foo::Bar" + #-- + #: () -> String def full_name full_name_parts.join("::") end @@ -243,10 +322,12 @@ def full_name # Previously, we had a child node on this class that contained either a # constant read or a missing node. To not cause a breaking change, we # continue to supply that API. + #-- + #: () -> (ConstantReadNode | MissingNode) def child deprecated("name", "name_loc") - if name + if (name = self.name) ConstantReadNode.new(source, -1, name_loc, 0, name) else MissingNode.new(source, -1, location, 0) @@ -257,11 +338,15 @@ def child class ConstantTargetNode < Node # Returns the list of parts for the full name of this constant. # For example: [:Foo] + #-- + #: () -> Array[Symbol] def full_name_parts [name] end # Returns the full name of this constant. For example: "Foo" + #-- + #: () -> String def full_name name.to_s end @@ -269,6 +354,8 @@ def full_name class ParametersNode < Node # Mirrors the Method#parameters method. + #-- + #: () -> Array[[Symbol, Symbol] | [Symbol]] def signature names = [] #: Array[[Symbol, Symbol] | [Symbol]] @@ -278,7 +365,7 @@ def signature optionals.each { |param| names << [:opt, param.name] } - if rest && rest.is_a?(RestParameterNode) + if (rest = self.rest).is_a?(RestParameterNode) names << [:rest, rest.name || :*] end @@ -309,7 +396,7 @@ def signature keyopt.each { |param| names << [:key, param.name] } - case keyword_rest + case (keyword_rest = self.keyword_rest) when ForwardingParameterNode names.concat([[:rest, :*], [:keyrest, :**], [:block, :&]]) when KeywordRestParameterNode @@ -318,7 +405,7 @@ def signature names << [:nokey] end - case block + case (block = self.block) when BlockParameterNode names << [:block, block.name || :&] when NoBlockParameterNode @@ -339,6 +426,8 @@ class CallNode < Node # can be any amount of space between the message and the = sign. However, # sometimes you want the location of the full message including the inner # space and the = sign. This method provides that. + #-- + #: () -> Location? def full_message_loc attribute_write? ? message_loc&.adjoin("=") : message_loc end @@ -347,6 +436,8 @@ def full_message_loc class CallOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -354,6 +445,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -363,6 +456,8 @@ def operator_loc class ClassVariableOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -370,6 +465,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -379,6 +476,8 @@ def operator_loc class ConstantOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -386,6 +485,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -395,6 +496,8 @@ def operator_loc class ConstantPathOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -402,6 +505,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -411,6 +516,8 @@ def operator_loc class GlobalVariableOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -418,6 +525,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -427,6 +536,8 @@ def operator_loc class IndexOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -434,6 +545,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -443,6 +556,8 @@ def operator_loc class InstanceVariableOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -450,6 +565,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -459,6 +576,8 @@ def operator_loc class LocalVariableOperatorWriteNode < Node # Returns the binary operator used to modify the receiver. This method is # deprecated in favor of #binary_operator. + #-- + #: () -> Symbol def operator deprecated("binary_operator") binary_operator @@ -466,6 +585,8 @@ def operator # Returns the location of the binary operator used to modify the receiver. # This method is deprecated in favor of #binary_operator_loc. + #-- + #: () -> Location def operator_loc deprecated("binary_operator_loc") binary_operator_loc @@ -475,6 +596,8 @@ def operator_loc class CaseMatchNode < Node # Returns the else clause of the case match node. This method is deprecated # in favor of #else_clause. + #-- + #: () -> ElseNode? def consequent deprecated("else_clause") else_clause @@ -484,6 +607,8 @@ def consequent class CaseNode < Node # Returns the else clause of the case node. This method is deprecated in # favor of #else_clause. + #-- + #: () -> ElseNode? def consequent deprecated("else_clause") else_clause @@ -493,6 +618,8 @@ def consequent class IfNode < Node # Returns the subsequent if/elsif/else clause of the if node. This method is # deprecated in favor of #subsequent. + #-- + #: () -> (IfNode | ElseNode)? def consequent deprecated("subsequent") subsequent @@ -502,6 +629,8 @@ def consequent class RescueNode < Node # Returns the subsequent rescue clause of the rescue node. This method is # deprecated in favor of #subsequent. + #-- + #: () -> RescueNode? def consequent deprecated("subsequent") subsequent @@ -511,6 +640,8 @@ def consequent class UnlessNode < Node # Returns the else clause of the unless node. This method is deprecated in # favor of #else_clause. + #-- + #: () -> ElseNode? def consequent deprecated("else_clause") else_clause diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 07529c4295cc87..e2e1145bdc60b9 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -1,7 +1,15 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism + # @rbs! + # # An internal interface for a cache that can be used to compute code + # # units from byte offsets. + # interface _CodeUnitsCache + # def []: (Integer byte_offset) -> Integer + # end + # This represents a source of Ruby code that has been parsed. It is used in # conjunction with locations to allow them to resolve line numbers and source # ranges. @@ -10,6 +18,8 @@ class Source # be used instead of `new` and it will return either a `Source` or a # specialized and more performant `ASCIISource` if no multibyte characters # are present in the source code. + #-- + #: (String source, ?Integer start_line, ?Array[Integer] offsets) -> Source def self.for(source, start_line = 1, offsets = []) if source.ascii_only? ASCIISource.new(source, start_line, offsets) @@ -34,15 +44,17 @@ def self.for(source, start_line = 1, offsets = []) end # The source code that this source object represents. - attr_reader :source + attr_reader :source #: String # The line number where this source starts. - attr_reader :start_line + attr_reader :start_line #: Integer # The list of newline byte offsets in the source code. - attr_reader :offsets + attr_reader :offsets #: Array[Integer] # Create a new source object with the given source code. + #-- + #: (String source, ?Integer start_line, ?Array[Integer] offsets) -> void def initialize(source, start_line = 1, offsets = []) @source = source @start_line = start_line # set after parsing is done @@ -50,33 +62,45 @@ def initialize(source, start_line = 1, offsets = []) end # Replace the value of start_line with the given value. + #-- + #: (Integer start_line) -> void def replace_start_line(start_line) @start_line = start_line end # Replace the value of offsets with the given value. + #-- + #: (Array[Integer] offsets) -> void def replace_offsets(offsets) @offsets.replace(offsets) end # Returns the encoding of the source code, which is set by parameters to the # parser or by the encoding magic comment. + #-- + #: () -> Encoding def encoding source.encoding end # Returns the lines of the source code as an array of strings. + #-- + #: () -> Array[String] def lines source.lines end # Perform a byteslice on the source code using the given byte offset and # byte length. + #-- + #: (Integer byte_offset, Integer length) -> String def slice(byte_offset, length) source.byteslice(byte_offset, length) or raise end # Converts the line number and column in bytes to a byte offset. + #-- + #: (Integer line, Integer column) -> Integer def byte_offset(line, column) normal = line - @start_line raise IndexError if normal < 0 @@ -87,33 +111,45 @@ def byte_offset(line, column) # Binary search through the offsets to find the line number for the given # byte offset. + #-- + #: (Integer byte_offset) -> Integer def line(byte_offset) start_line + find_line(byte_offset) end # Return the byte offset of the start of the line corresponding to the given # byte offset. + #-- + #: (Integer byte_offset) -> Integer def line_start(byte_offset) offsets[find_line(byte_offset)] end # Returns the byte offset of the end of the line corresponding to the given # byte offset. + #-- + #: (Integer byte_offset) -> Integer def line_end(byte_offset) offsets[find_line(byte_offset) + 1] || source.bytesize end # Return the column in bytes for the given byte offset. + #-- + #: (Integer byte_offset) -> Integer def column(byte_offset) byte_offset - line_start(byte_offset) end # Return the character offset for the given byte offset. + #-- + #: (Integer byte_offset) -> Integer def character_offset(byte_offset) (source.byteslice(0, byte_offset) or raise).length end # Return the column in characters for the given byte offset. + #-- + #: (Integer byte_offset) -> Integer def character_column(byte_offset) character_offset(byte_offset) - character_offset(line_start(byte_offset)) end @@ -130,6 +166,8 @@ def character_column(byte_offset) # possible that the given byte offset will not occur on a character # boundary. Second, it's possible that the source code will contain a # character that has no equivalent in the given encoding. + #-- + #: (Integer byte_offset, Encoding encoding) -> Integer def code_units_offset(byte_offset, encoding) byteslice = (source.byteslice(0, byte_offset) or raise).encode(encoding, invalid: :replace, undef: :replace) @@ -142,17 +180,23 @@ def code_units_offset(byte_offset, encoding) # Generate a cache that targets a specific encoding for calculating code # unit offsets. + #-- + #: (Encoding encoding) -> CodeUnitsCache def code_units_cache(encoding) CodeUnitsCache.new(source, encoding) end # Returns the column in code units for the given encoding for the # given byte offset. + #-- + #: (Integer byte_offset, Encoding encoding) -> Integer def code_units_column(byte_offset, encoding) code_units_offset(byte_offset, encoding) - code_units_offset(line_start(byte_offset), encoding) end # Freeze this object and the objects it contains. + #-- + #: () -> void def deep_freeze source.freeze offsets.freeze @@ -163,6 +207,8 @@ def deep_freeze # Binary search through the offsets to find the line number for the given # byte offset. + #-- + #: (Integer byte_offset) -> Integer def find_line(byte_offset) # :nodoc: index = offsets.bsearch_index { |offset| offset > byte_offset } || offsets.length index - 1 @@ -185,30 +231,47 @@ def find_line(byte_offset) # :nodoc: # class CodeUnitsCache class UTF16Counter # :nodoc: + # @rbs @source: String + # @rbs @encoding: Encoding + + #: (String source, Encoding encoding) -> void def initialize(source, encoding) @source = source @encoding = encoding end + #: (Integer byte_offset, Integer byte_length) -> Integer def count(byte_offset, byte_length) - @source.byteslice(byte_offset, byte_length).encode(@encoding, invalid: :replace, undef: :replace).bytesize / 2 + (@source.byteslice(byte_offset, byte_length) or raise).encode(@encoding, invalid: :replace, undef: :replace).bytesize / 2 end end class LengthCounter # :nodoc: + # @rbs @source: String + # @rbs @encoding: Encoding + + #: (String source, Encoding encoding) -> void def initialize(source, encoding) @source = source @encoding = encoding end + #: (Integer byte_offset, Integer byte_length) -> Integer def count(byte_offset, byte_length) - @source.byteslice(byte_offset, byte_length).encode(@encoding, invalid: :replace, undef: :replace).length + (@source.byteslice(byte_offset, byte_length) or raise).encode(@encoding, invalid: :replace, undef: :replace).length end end private_constant :UTF16Counter, :LengthCounter + # @rbs @source: String + # @rbs @counter: UTF16Counter | LengthCounter + # @rbs @cache: Hash[Integer, Integer] + # @rbs @offsets: Array[Integer] + # Initialize a new cache with the given source and encoding. + #-- + #: (String source, Encoding encoding) -> void def initialize(source, encoding) @source = source @counter = @@ -223,6 +286,8 @@ def initialize(source, encoding) end # Retrieve the code units offset from the given byte offset. + #-- + #: (Integer byte_offset) -> Integer def [](byte_offset) @cache[byte_offset] ||= if (index = @offsets.bsearch_index { |offset| offset > byte_offset }).nil? @@ -249,11 +314,15 @@ def [](byte_offset) # at that point we will treat everything as single-byte characters. class ASCIISource < Source # Return the character offset for the given byte offset. + #-- + #: (Integer byte_offset) -> Integer def character_offset(byte_offset) byte_offset end # Return the column in characters for the given byte offset. + #-- + #: (Integer byte_offset) -> Integer def character_column(byte_offset) byte_offset - line_start(byte_offset) end @@ -264,6 +333,8 @@ def character_column(byte_offset) # This method is tested with UTF-8, UTF-16, and UTF-32. If there is the # concept of code units that differs from the number of characters in other # encodings, it is not captured here. + #-- + #: (Integer byte_offset, Encoding encoding) -> Integer def code_units_offset(byte_offset, encoding) byte_offset end @@ -271,6 +342,8 @@ def code_units_offset(byte_offset, encoding) # Returns a cache that is the identity function in order to maintain the # same interface. We can do this because code units are always equivalent to # byte offsets for ASCII-only sources. + #-- + #: (Encoding encoding) -> _CodeUnitsCache def code_units_cache(encoding) ->(byte_offset) { byte_offset } end @@ -278,6 +351,8 @@ def code_units_cache(encoding) # Specialized version of `code_units_column` that does not depend on # `code_units_offset`, which is a more expensive operation. This is # essentially the same as `Prism::Source#column`. + #-- + #: (Integer byte_offset, Encoding encoding) -> Integer def code_units_column(byte_offset, encoding) byte_offset - line_start(byte_offset) end @@ -287,18 +362,23 @@ def code_units_column(byte_offset, encoding) class Location # A Source object that is used to determine more information from the given # offset and length. - attr_reader :source + attr_reader :source #: Source protected :source # The byte offset from the beginning of the source where this location # starts. - attr_reader :start_offset + attr_reader :start_offset #: Integer # The length of this location in bytes. - attr_reader :length + attr_reader :length #: Integer + + # @rbs @leading_comments: Array[Comment]? + # @rbs @trailing_comments: Array[Comment]? # Create a new location object with the given source, start byte offset, and # byte length. + #-- + #: (Source source, Integer start_offset, Integer length) -> void def initialize(source, start_offset, length) @source = source @start_offset = start_offset @@ -313,53 +393,73 @@ def initialize(source, start_offset, length) # These are the comments that are associated with this location that exist # before the start of this location. + #-- + #: () -> Array[Comment] def leading_comments @leading_comments ||= [] end # Attach a comment to the leading comments of this location. + #-- + #: (Comment comment) -> void def leading_comment(comment) leading_comments << comment end # These are the comments that are associated with this location that exist # after the end of this location. + #-- + #: () -> Array[Comment] def trailing_comments @trailing_comments ||= [] end # Attach a comment to the trailing comments of this location. + #-- + #: (Comment comment) -> void def trailing_comment(comment) trailing_comments << comment end # Returns all comments that are associated with this location (both leading # and trailing comments). + #-- + #: () -> Array[Comment] def comments - [*@leading_comments, *@trailing_comments] + [*@leading_comments, *@trailing_comments] #: Array[Comment] end # Create a new location object with the given options. + #-- + #: (?source: Source, ?start_offset: Integer, ?length: Integer) -> Location def copy(source: self.source, start_offset: self.start_offset, length: self.length) Location.new(source, start_offset, length) end # Returns a new location that is the result of chopping off the last byte. + #-- + #: () -> Location def chop copy(length: length == 0 ? length : length - 1) end # Returns a string representation of this location. + #-- + #: () -> String def inspect # :nodoc: "#" end # Returns all of the lines of the source code associated with this location. + #-- + #: () -> Array[String] def source_lines source.lines end # The source code that this location represents. + #-- + #: () -> String def slice source.slice(start_offset, length) end @@ -367,6 +467,8 @@ def slice # The source code that this location represents starting from the beginning # of the line that this location starts on to the end of the line that this # location ends on. + #-- + #: () -> String def slice_lines line_start = source.line_start(start_offset) line_end = source.line_end(end_offset) @@ -375,118 +477,160 @@ def slice_lines # The character offset from the beginning of the source where this location # starts. + #-- + #: () -> Integer def start_character_offset source.character_offset(start_offset) end # The offset from the start of the file in code units of the given encoding. + #-- + #: (Encoding encoding) -> Integer def start_code_units_offset(encoding = Encoding::UTF_16LE) source.code_units_offset(start_offset, encoding) end # The start offset from the start of the file in code units using the given # cache to fetch or calculate the value. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_start_code_units_offset(cache) cache[start_offset] end # The byte offset from the beginning of the source where this location ends. + #-- + #: () -> Integer def end_offset start_offset + length end # The character offset from the beginning of the source where this location # ends. + #-- + #: () -> Integer def end_character_offset source.character_offset(end_offset) end # The offset from the start of the file in code units of the given encoding. + #-- + #: (Encoding encoding) -> Integer def end_code_units_offset(encoding = Encoding::UTF_16LE) source.code_units_offset(end_offset, encoding) end # The end offset from the start of the file in code units using the given # cache to fetch or calculate the value. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_end_code_units_offset(cache) cache[end_offset] end # The line number where this location starts. + #-- + #: () -> Integer def start_line source.line(start_offset) end # The content of the line where this location starts before this location. + #-- + #: () -> String def start_line_slice offset = source.line_start(start_offset) source.slice(offset, start_offset - offset) end # The line number where this location ends. + #-- + #: () -> Integer def end_line source.line(end_offset) end # The column in bytes where this location starts from the start of # the line. + #-- + #: () -> Integer def start_column source.column(start_offset) end # The column in characters where this location ends from the start of # the line. + #-- + #: () -> Integer def start_character_column source.character_column(start_offset) end # The column in code units of the given encoding where this location # starts from the start of the line. + #-- + #: (?Encoding encoding) -> Integer def start_code_units_column(encoding = Encoding::UTF_16LE) source.code_units_column(start_offset, encoding) end # The start column in code units using the given cache to fetch or calculate # the value. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_start_code_units_column(cache) cache[start_offset] - cache[source.line_start(start_offset)] end # The column in bytes where this location ends from the start of the # line. + #-- + #: () -> Integer def end_column source.column(end_offset) end # The column in characters where this location ends from the start of # the line. + #-- + #: () -> Integer def end_character_column source.character_column(end_offset) end # The column in code units of the given encoding where this location # ends from the start of the line. + #-- + #: (?Encoding encoding) -> Integer def end_code_units_column(encoding = Encoding::UTF_16LE) source.code_units_column(end_offset, encoding) end # The end column in code units using the given cache to fetch or calculate # the value. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_end_code_units_column(cache) cache[end_offset] - cache[source.line_start(end_offset)] end # Implement the hash pattern matching interface for Location. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { start_offset: start_offset, end_offset: end_offset } end # Implement the pretty print interface for Location. + #-- + #: (PP q) -> void def pretty_print(q) # :nodoc: q.text("(#{start_line},#{start_column})-(#{end_line},#{end_column})") end # Returns true if the given other location is equal to this location. + #-- + #: (untyped other) -> bool def ==(other) Location === other && other.start_offset == start_offset && @@ -496,6 +640,8 @@ def ==(other) # Returns a new location that stretches from this location to the given # other location. Raises an error if this location is not before the other # location or if they don't share the same source. + #-- + #: (Location other) -> Location def join(other) raise "Incompatible sources" if source != other.source raise "Incompatible locations" if start_offset > other.start_offset @@ -506,6 +652,8 @@ def join(other) # Join this location with the first occurrence of the string in the source # that occurs after this location on the same line, and return the new # location. This will raise an error if the string does not exist. + #-- + #: (String string) -> Location def adjoin(string) line_suffix = source.slice(end_offset, source.line_end(end_offset) - end_offset) @@ -520,22 +668,37 @@ def adjoin(string) # base class for all comment types. class Comment # The Location of this comment in the source. - attr_reader :location + attr_reader :location #: Location # Create a new comment object with the given location. + #-- + #: (Location location) -> void def initialize(location) @location = location end # Implement the hash pattern matching interface for Comment. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { location: location } end # Returns the content of the comment by slicing it from the source code. + #-- + #: () -> String def slice location.slice end + + # Returns true if this comment happens on the same line as other code and + # false if the comment is by itself. This can only be true for inline + # comments and should be false for block comments. + #-- + #: () -> bool + def trailing? + raise NotImplementedError, "trailing? is not implemented for #{self.class}" + end end # InlineComment objects are the most common. They correspond to comments in @@ -543,11 +706,15 @@ def slice class InlineComment < Comment # Returns true if this comment happens on the same line as other code and # false if the comment is by itself. + #-- + #: () -> bool def trailing? !location.start_line_slice.strip.empty? end # Returns a string representation of this comment. + #-- + #: () -> String def inspect # :nodoc: "#" end @@ -557,11 +724,15 @@ def inspect # :nodoc: # and =end. class EmbDocComment < Comment # Returns false. This can only be true for inline comments. + #-- + #: () -> bool def trailing? false end # Returns a string representation of this comment. + #-- + #: () -> String def inspect # :nodoc: "#" end @@ -570,33 +741,43 @@ def inspect # :nodoc: # This represents a magic comment that was encountered during parsing. class MagicComment # A Location object representing the location of the key in the source. - attr_reader :key_loc + attr_reader :key_loc #: Location # A Location object representing the location of the value in the source. - attr_reader :value_loc + attr_reader :value_loc #: Location # Create a new magic comment object with the given key and value locations. + #-- + #: (Location key_loc, Location value_loc) -> void def initialize(key_loc, value_loc) @key_loc = key_loc @value_loc = value_loc end # Returns the key of the magic comment by slicing it from the source code. + #-- + #: () -> String def key key_loc.slice end # Returns the value of the magic comment by slicing it from the source code. + #-- + #: () -> String def value value_loc.slice end # Implement the hash pattern matching interface for MagicComment. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { key_loc: key_loc, value_loc: value_loc } end # Returns a string representation of this magic comment. + #-- + #: () -> String def inspect # :nodoc: "#" end @@ -606,18 +787,20 @@ def inspect # :nodoc: class ParseError # The type of error. This is an _internal_ symbol that is used for # communicating with translation layers. It is not meant to be public API. - attr_reader :type + attr_reader :type #: Symbol # The message associated with this error. - attr_reader :message + attr_reader :message #: String # A Location object representing the location of this error in the source. - attr_reader :location + attr_reader :location #: Location # The level of this error. - attr_reader :level + attr_reader :level #: Symbol # Create a new error object with the given message and location. + #-- + #: (Symbol type, String message, Location location, Symbol level) -> void def initialize(type, message, location, level) @type = type @message = message @@ -626,11 +809,15 @@ def initialize(type, message, location, level) end # Implement the hash pattern matching interface for ParseError. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { type: type, message: message, location: location, level: level } end # Returns a string representation of this error. + #-- + #: () -> String def inspect # :nodoc: "#" end @@ -640,18 +827,20 @@ def inspect # :nodoc: class ParseWarning # The type of warning. This is an _internal_ symbol that is used for # communicating with translation layers. It is not meant to be public API. - attr_reader :type + attr_reader :type #: Symbol # The message associated with this warning. - attr_reader :message + attr_reader :message #: String # A Location object representing the location of this warning in the source. - attr_reader :location + attr_reader :location #: Location # The level of this warning. - attr_reader :level + attr_reader :level #: Symbol # Create a new warning object with the given message and location. + #-- + #: (Symbol type, String message, Location location, Symbol level) -> void def initialize(type, message, location, level) @type = type @message = message @@ -660,11 +849,15 @@ def initialize(type, message, location, level) end # Implement the hash pattern matching interface for ParseWarning. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { type: type, message: message, location: location, level: level } end # Returns a string representation of this warning. + #-- + #: () -> String def inspect # :nodoc: "#" end @@ -675,26 +868,28 @@ def inspect # :nodoc: # and any errors that were encountered. class Result # The list of comments that were encountered during parsing. - attr_reader :comments + attr_reader :comments #: Array[Comment] # The list of magic comments that were encountered during parsing. - attr_reader :magic_comments + attr_reader :magic_comments #: Array[MagicComment] # An optional location that represents the location of the __END__ marker # and the rest of the content of the file. This content is loaded into the # DATA constant when the file being parsed is the main file being executed. - attr_reader :data_loc + attr_reader :data_loc #: Location? # The list of errors that were generated during parsing. - attr_reader :errors + attr_reader :errors #: Array[ParseError] # The list of warnings that were generated during parsing. - attr_reader :warnings + attr_reader :warnings #: Array[ParseWarning] # A Source instance that represents the source code that was parsed. - attr_reader :source + attr_reader :source #: Source # Create a new result object with the given values. + #-- + #: (Array[Comment] comments, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, Source source) -> void def initialize(comments, magic_comments, data_loc, errors, warnings, source) @comments = comments @magic_comments = magic_comments @@ -705,28 +900,38 @@ def initialize(comments, magic_comments, data_loc, errors, warnings, source) end # Implement the hash pattern matching interface for Result. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { comments: comments, magic_comments: magic_comments, data_loc: data_loc, errors: errors, warnings: warnings } end # Returns the encoding of the source code that was parsed. + #-- + #: () -> Encoding def encoding source.encoding end # Returns true if there were no errors during parsing and false if there # were. + #-- + #: () -> bool def success? errors.empty? end # Returns true if there were errors during parsing and false if there were # not. + #-- + #: () -> bool def failure? !success? end # Create a code units cache for the given encoding. + #-- + #: (Encoding encoding) -> _CodeUnitsCache def code_units_cache(encoding) source.code_units_cache(encoding) end @@ -743,32 +948,42 @@ class ParseResult < Result private_constant :Newlines # The syntax tree that was parsed from the source code. - attr_reader :value + attr_reader :value #: ProgramNode # Create a new parse result object with the given values. + #-- + #: (ProgramNode value, Array[Comment] comments, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, Source source) -> void def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) @value = value super(comments, magic_comments, data_loc, errors, warnings, source) end # Implement the hash pattern matching interface for ParseResult. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: super.merge!(value: value) end # Attach the list of comments to their respective locations in the tree. + #-- + #: () -> void def attach_comments! Comments.new(self).attach! # steep:ignore end # Walk the tree and mark nodes that are on a new line, loosely emulating # the behavior of CRuby's `:line` tracepoint event. + #-- + #: () -> void def mark_newlines! value.accept(Newlines.new(source.offsets.size)) # steep:ignore end # Returns a string representation of the syntax tree with the errors # displayed inline. + #-- + #: () -> String def errors_format Errors.new(self).format end @@ -777,15 +992,19 @@ def errors_format # This is a result specific to the `lex` and `lex_file` methods. class LexResult < Result # The list of tokens that were parsed from the source code. - attr_reader :value + attr_reader :value #: Array[[Token, Integer]] # Create a new lex result object with the given values. + #-- + #: (Array[[Token, Integer]] value, Array[Comment] comments, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, Source source) -> void def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) @value = value super(comments, magic_comments, data_loc, errors, warnings, source) end # Implement the hash pattern matching interface for LexResult. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: super.merge!(value: value) end @@ -795,15 +1014,19 @@ def deconstruct_keys(keys) # :nodoc: class ParseLexResult < Result # A tuple of the syntax tree and the list of tokens that were parsed from # the source code. - attr_reader :value + attr_reader :value #: [ProgramNode, Array[[Token, Integer]]] # Create a new parse lex result object with the given values. + #-- + #: ([ProgramNode, Array[[Token, Integer]]] value, Array[Comment] comments, Array[MagicComment] magic_comments, Location? data_loc, Array[ParseError] errors, Array[ParseWarning] warnings, Source source) -> void def initialize(value, comments, magic_comments, data_loc, errors, warnings, source) @value = value super(comments, magic_comments, data_loc, errors, warnings, source) end # Implement the hash pattern matching interface for ParseLexResult. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: super.merge!(value: value) end @@ -812,16 +1035,20 @@ def deconstruct_keys(keys) # :nodoc: # This represents a token from the Ruby source. class Token # The Source object that represents the source this token came from. - attr_reader :source + attr_reader :source #: Source private :source # The type of token that this token is. - attr_reader :type + attr_reader :type #: Symbol # A byteslice of the source that this token represents. - attr_reader :value + attr_reader :value #: String + + # @rbs @location: Location | Integer # Create a new token object with the given type, value, and location. + #-- + #: (Source source, Symbol type, String value, Location | Integer location) -> void def initialize(source, type, value, location) @source = source @type = type @@ -830,11 +1057,15 @@ def initialize(source, type, value, location) end # Implement the hash pattern matching interface for Token. + #-- + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { type: type, value: value, location: location } end # A Location object representing the location of this token in the source. + #-- + #: () -> Location def location location = @location return location if location.is_a?(Location) @@ -842,6 +1073,8 @@ def location end # Implement the pretty print interface for Token. + #-- + #: (PP q) -> void def pretty_print(q) # :nodoc: q.group do q.text(type.to_s) @@ -857,6 +1090,8 @@ def pretty_print(q) # :nodoc: end # Returns true if the given other token is equal to this token. + #-- + #: (untyped other) -> bool def ==(other) Token === other && other.type == type && @@ -864,12 +1099,16 @@ def ==(other) end # Returns a string representation of this token. + #-- + #: () -> String def inspect # :nodoc: location super end # Freeze this object and the objects it contains. + #-- + #: () -> void def deep_freeze value.freeze location.freeze @@ -884,14 +1123,16 @@ def deep_freeze class Scope # The list of local variables that are defined in this scope. This should be # defined as an array of symbols. - attr_reader :locals + attr_reader :locals #: Array[Symbol] # The list of local variables that are forwarded to the next scope. This # should by defined as an array of symbols containing the specific values of # :*, :**, :&, or :"...". - attr_reader :forwarding + attr_reader :forwarding #: Array[Symbol] # Create a new scope object with the given locals and forwarding. + #-- + #: (Array[Symbol] locals, Array[Symbol] forwarding) -> void def initialize(locals, forwarding) @locals = locals @forwarding = forwarding @@ -901,6 +1142,8 @@ def initialize(locals, forwarding) # Create a new scope with the given locals and forwarding options that is # suitable for passing into one of the Prism.* methods that accepts the # `scopes` option. + #-- + #: (?locals: Array[Symbol], ?forwarding: Array[Symbol]) -> Scope def self.scope(locals: [], forwarding: []) Scope.new(locals, forwarding) end diff --git a/lib/prism/parse_result/comments.rb b/lib/prism/parse_result/comments.rb index 3e93316aff8662..7b54ce7fd65386 100644 --- a/lib/prism/parse_result/comments.rb +++ b/lib/prism/parse_result/comments.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism @@ -18,32 +19,49 @@ class ParseResult < Result # the comment. Otherwise it will favor attaching to the nearest location # that is after the comment. class Comments + # @rbs! + # # An internal interface for a target that comments can be attached + # # to. This is either going to be a NodeTarget or a CommentTarget. + # interface _CommentTarget + # def start_offset: () -> Integer + # def end_offset: () -> Integer + # def encloses?: (Comment) -> bool + # def leading_comment: (Comment) -> void + # def trailing_comment: (Comment) -> void + # end + # A target for attaching comments that is based on a specific node's # location. class NodeTarget # :nodoc: - attr_reader :node + attr_reader :node #: node + #: (node node) -> void def initialize(node) @node = node end + #: () -> Integer def start_offset node.start_offset end + #: () -> Integer def end_offset node.end_offset end + #: (Comment comment) -> bool def encloses?(comment) start_offset <= comment.location.start_offset && comment.location.end_offset <= end_offset end + #: (Comment comment) -> void def leading_comment(comment) node.location.leading_comment(comment) end + #: (Comment comment) -> void def trailing_comment(comment) node.location.trailing_comment(comment) end @@ -52,44 +70,54 @@ def trailing_comment(comment) # A target for attaching comments that is based on a location field on a # node. For example, the `end` token of a ClassNode. class LocationTarget # :nodoc: - attr_reader :location + attr_reader :location #: Location + #: (Location location) -> void def initialize(location) @location = location end + #: () -> Integer def start_offset location.start_offset end + #: () -> Integer def end_offset location.end_offset end + #: (Comment comment) -> bool def encloses?(comment) false end + #: (Comment comment) -> void def leading_comment(comment) location.leading_comment(comment) end + #: (Comment comment) -> void def trailing_comment(comment) location.trailing_comment(comment) end end # The parse result that we are attaching comments to. - attr_reader :parse_result + attr_reader :parse_result #: ParseResult # Create a new Comments object that will attach comments to the given # parse result. + #-- + #: (ParseResult parse_result) -> void def initialize(parse_result) @parse_result = parse_result end # Attach the comments to their respective locations in the tree by # mutating the parse result. + #-- + #: () -> void def attach! parse_result.comments.each do |comment| preceding, enclosing, following = nearest_targets(parse_result.value, comment) @@ -117,11 +145,13 @@ def attach! # Responsible for finding the nearest targets to the given comment within # the context of the given encapsulating node. + #-- + #: (node node, Comment comment) -> [_CommentTarget?, _CommentTarget?, _CommentTarget?] def nearest_targets(node, comment) comment_start = comment.location.start_offset comment_end = comment.location.end_offset - targets = [] #: Array[_Target] + targets = [] #: Array[_CommentTarget] node.comment_targets.map do |value| case value when StatementsNode @@ -134,8 +164,8 @@ def nearest_targets(node, comment) end targets.sort_by!(&:start_offset) - preceding = nil #: _Target? - following = nil #: _Target? + preceding = nil #: _CommentTarget? + following = nil #: _CommentTarget? left = 0 right = targets.length diff --git a/lib/prism/parse_result/errors.rb b/lib/prism/parse_result/errors.rb index 26c376b3ce895f..03d65daecf0552 100644 --- a/lib/prism/parse_result/errors.rb +++ b/lib/prism/parse_result/errors.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown require "stringio" @@ -9,14 +10,18 @@ class ParseResult < Result # can be used to format the errors in a human-readable way. class Errors # The parse result that contains the errors. - attr_reader :parse_result + attr_reader :parse_result #: ParseResult # Initialize a new set of errors from the given parse result. + #-- + #: (ParseResult parse_result) -> void def initialize(parse_result) @parse_result = parse_result end # Formats the errors in a human-readable way and return them as a string. + #-- + #: () -> String def format error_lines = {} #: Hash[Integer, Array[ParseError]] parse_result.errors.each do |error| diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb index e7fd62cafedb37..cfbc1ea1d0e97f 100644 --- a/lib/prism/parse_result/newlines.rb +++ b/lib/prism/parse_result/newlines.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism @@ -24,13 +25,20 @@ class ParseResult < Result # that case. We do that to avoid storing the extra `@newline` instance # variable on every node if we don't need it. class Newlines < Visitor + # The map of lines indices to whether or not they have been marked as + # emitting a newline event. + # @rbs @lines: Array[bool] + # Create a new Newlines visitor with the given newline offsets. + #-- + #: (Integer lines) -> void def initialize(lines) - # @type var lines: Integer @lines = Array.new(1 + lines, false) end - # Permit block/lambda nodes to mark newlines within themselves. + # Permit block nodes to mark newlines within themselves. + #-- + #: (BlockNode node) -> void def visit_block_node(node) old_lines = @lines @lines = Array.new(old_lines.size, false) @@ -42,17 +50,39 @@ def visit_block_node(node) end end - alias_method :visit_lambda_node, :visit_block_node + # Permit lambda nodes to mark newlines within themselves. + #-- + #: (LambdaNode node) -> void + def visit_lambda_node(node) + old_lines = @lines + @lines = Array.new(old_lines.size, false) + + begin + super(node) + ensure + @lines = old_lines + end + end - # Mark if/unless nodes as newlines. + # Mark if nodes as newlines. + #-- + #: (IfNode node) -> void def visit_if_node(node) node.newline_flag!(@lines) super(node) end - alias_method :visit_unless_node, :visit_if_node + # Mark unless nodes as newlines. + #-- + #: (UnlessNode node) -> void + def visit_unless_node(node) + node.newline_flag!(@lines) + super(node) + end # Permit statements lists to mark newlines within themselves. + #-- + #: (StatementsNode node) -> void def visit_statements_node(node) node.body.each do |child| child.newline_flag!(@lines) @@ -63,10 +93,16 @@ def visit_statements_node(node) end class Node + # Tracks whether or not this node should emit a newline event when the + # instructions that it represents are executed. + # @rbs @newline_flag: bool + + #: () -> bool def newline_flag? # :nodoc: !!defined?(@newline_flag) end + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: line = location.start_line unless lines[line] @@ -77,48 +113,56 @@ def newline_flag!(lines) # :nodoc: end class BeginNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: # Never mark BeginNode with a newline flag, mark children instead. end end class ParenthesesNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: # Never mark ParenthesesNode with a newline flag, mark children instead. end end class IfNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class UnlessNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class UntilNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class WhileNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class RescueModifierNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: expression.newline_flag!(lines) end end class InterpolatedMatchLastLineNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -126,6 +170,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedRegularExpressionNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -133,6 +178,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedStringNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -140,6 +186,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedSymbolNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -147,6 +194,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedXStringNode < Node + # @rbs override def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb index dde9d3b6f96b06..b15b04d9bc97c0 100644 --- a/lib/prism/pattern.rb +++ b/lib/prism/pattern.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism @@ -41,6 +42,8 @@ class Pattern class CompilationError < StandardError # Create a new CompilationError with the given representation of the node # that caused the error. + #-- + #: (String repr) -> void def initialize(repr) # :nodoc: super(<<~ERROR) prism was unable to compile the pattern you provided into a usable @@ -57,10 +60,13 @@ def initialize(repr) # :nodoc: end # The query that this pattern was initialized with. - attr_reader :query + attr_reader :query #: String + # @rbs @compiled: Proc? # Create a new pattern with the given query. The query should be a string # containing a Ruby pattern matching expression. + #-- + #: (String query) -> void def initialize(query) @query = query @compiled = nil @@ -68,6 +74,8 @@ def initialize(query) # Compile the query into a callable object that can be used to match against # nodes. + #-- + #: () -> Proc def compile result = Prism.parse("case nil\nin #{query}\nend") @@ -84,6 +92,9 @@ def compile # pattern. If a block is given, it will be called with each node that # matches the pattern. If no block is given, an enumerator will be returned # that will yield each node that matches the pattern. + #-- + #: (node root) { (node) -> void } -> void + #: (node root) -> Enumerator[node, void] def scan(root) return to_enum(:scan, root) unless block_given? @@ -100,22 +111,32 @@ def scan(root) # Shortcut for combining two procs into one that returns true if both return # true. + #-- + #: (Proc left, Proc right) -> Proc def combine_and(left, right) # :nodoc: ->(other) { left.call(other) && right.call(other) } end # Shortcut for combining two procs into one that returns true if either # returns true. + #-- + #: (Proc left, Proc right) -> Proc def combine_or(left, right) # :nodoc: ->(other) { left.call(other) || right.call(other) } end - # Raise an error because the given node is not supported. + # Raise an error because the given node is not supported. Note purposefully + # not typing this method since it is a no return method that Steep does not + # understand. + #-- + #: (node node) -> bot def compile_error(node) # :nodoc: raise CompilationError, node.inspect end # in [foo, bar, baz] + #-- + #: (ArrayPatternNode node) -> Proc def compile_array_pattern_node(node) # :nodoc: compile_error(node) if !node.rest.nil? || node.posts.any? @@ -141,11 +162,15 @@ def compile_array_pattern_node(node) # :nodoc: end # in foo | bar + #-- + #: (AlternationPatternNode node) -> Proc def compile_alternation_pattern_node(node) # :nodoc: combine_or(compile_node(node.left), compile_node(node.right)) end # in Prism::ConstantReadNode + #-- + #: (ConstantPathNode node) -> Proc def compile_constant_path_node(node) # :nodoc: parent = node.parent @@ -161,11 +186,15 @@ def compile_constant_path_node(node) # :nodoc: # in ConstantReadNode # in String + #-- + #: (ConstantReadNode node) -> Proc def compile_constant_read_node(node) # :nodoc: compile_constant_name(node, node.name) end # Compile a name associated with a constant. + #-- + #: ((ConstantPathNode | ConstantReadNode) node, Symbol name) -> Proc def compile_constant_name(node, name) # :nodoc: if Prism.const_defined?(name, false) clazz = Prism.const_get(name) @@ -182,9 +211,14 @@ def compile_constant_name(node, name) # :nodoc: # in InstanceVariableReadNode[name: Symbol] # in { name: Symbol } + #-- + #: (HashPatternNode node) -> Proc def compile_hash_pattern_node(node) # :nodoc: compile_error(node) if node.rest - compiled_constant = compile_node(node.constant) if node.constant + + if (constant = node.constant) + compiled_constant = compile_node(constant) + end preprocessed = node.elements.to_h do |element| @@ -212,11 +246,15 @@ def compile_hash_pattern_node(node) # :nodoc: end # in nil + #-- + #: (NilNode node) -> Proc def compile_nil_node(node) # :nodoc: ->(attribute) { attribute.nil? } end # in /foo/ + #-- + #: (RegularExpressionNode node) -> Proc def compile_regular_expression_node(node) # :nodoc: regexp = Regexp.new(node.unescaped, node.closing[1..]) @@ -225,6 +263,8 @@ def compile_regular_expression_node(node) # :nodoc: # in "" # in "foo" + #-- + #: (StringNode node) -> Proc def compile_string_node(node) # :nodoc: string = node.unescaped @@ -233,6 +273,8 @@ def compile_string_node(node) # :nodoc: # in :+ # in :foo + #-- + #: (SymbolNode node) -> Proc def compile_symbol_node(node) # :nodoc: symbol = node.unescaped.to_sym @@ -241,6 +283,8 @@ def compile_symbol_node(node) # :nodoc: # Compile any kind of node. Dispatch out to the individual compilation # methods based on the type of node. + #-- + #: (node node) -> Proc def compile_node(node) # :nodoc: case node when AlternationPatternNode diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index dde7e711e2e580..f3cba41a4338bf 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -123,24 +123,28 @@ Gem::Specification.new do |spec| "rbi/prism/translation/parser_versions.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/visitor.rbi", - "sig/prism.rbs", - "sig/prism/compiler.rbs", - "sig/prism/dispatcher.rbs", - "sig/prism/dot_visitor.rbs", - "sig/prism/dsl.rbs", - "sig/prism/inspect_visitor.rbs", - "sig/prism/lex_compat.rbs", - "sig/prism/mutation_compiler.rbs", - "sig/prism/node_ext.rbs", - "sig/prism/node.rbs", - "sig/prism/parse_result.rbs", - "sig/prism/parse_result/comments.rbs", - "sig/prism/pattern.rbs", - "sig/prism/reflection.rbs", - "sig/prism/relocation.rbs", - "sig/prism/serialize.rbs", - "sig/prism/string_query.rbs", - "sig/prism/visitor.rbs", + "sig/generated/prism.rbs", + "sig/generated/prism/compiler.rbs", + "sig/generated/prism/desugar_compiler.rbs", + "sig/generated/prism/dispatcher.rbs", + "sig/generated/prism/dot_visitor.rbs", + "sig/generated/prism/dsl.rbs", + "sig/generated/prism/inspect_visitor.rbs", + "sig/generated/prism/lex_compat.rbs", + "sig/generated/prism/mutation_compiler.rbs", + "sig/generated/prism/node.rbs", + "sig/generated/prism/node_ext.rbs", + "sig/generated/prism/parse_result.rbs", + "sig/generated/prism/pattern.rbs", + "sig/generated/prism/reflection.rbs", + "sig/generated/prism/relocation.rbs", + "sig/generated/prism/serialize.rbs", + "sig/generated/prism/string_query.rbs", + "sig/generated/prism/translation.rbs", + "sig/generated/prism/visitor.rbs", + "sig/generated/prism/parse_result/comments.rbs", + "sig/generated/prism/parse_result/errors.rbs", + "sig/generated/prism/parse_result/newlines.rbs", "src/diagnostic.c", "src/encoding.c", "src/node.c", diff --git a/lib/prism/relocation.rb b/lib/prism/relocation.rb index 3e9210a7853431..2ac471d4250ebc 100644 --- a/lib/prism/relocation.rb +++ b/lib/prism/relocation.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism @@ -12,6 +13,33 @@ module Prism # "save" nodes and locations using a minimal amount of memory (just the # node_id and a field identifier) and then reify them later. module Relocation + # @rbs! + # type entry_value = untyped + # type entry_values = Hash[Symbol, entry_value] + # + # interface _Value + # def start_line: () -> Integer + # def end_line: () -> Integer + # def start_offset: () -> Integer + # def end_offset: () -> Integer + # def start_character_offset: () -> Integer + # def end_character_offset: () -> Integer + # def cached_start_code_units_offset: (_CodeUnitsCache cache) -> Integer + # def cached_end_code_units_offset: (_CodeUnitsCache cache) -> Integer + # def start_column: () -> Integer + # def end_column: () -> Integer + # def start_character_column: () -> Integer + # def end_character_column: () -> Integer + # def cached_start_code_units_column: (_CodeUnitsCache cache) -> Integer + # def cached_end_code_units_column: (_CodeUnitsCache cache) -> Integer + # def leading_comments: () -> Array[Comment] + # def trailing_comments: () -> Array[Comment] + # end + # + # interface _Field + # def fields: (_Value value) -> entry_values + # end + # An entry in a repository that will lazily reify its values when they are # first accessed. class Entry @@ -21,109 +49,152 @@ class Entry class MissingValueError < StandardError end + # @rbs @repository: Repository? + # @rbs @values: Hash[Symbol, untyped]? + # Initialize a new entry with the given repository. + #-- + #: (Repository repository) -> void def initialize(repository) @repository = repository @values = nil end # Fetch the filepath of the value. + #-- + #: () -> String def filepath fetch_value(:filepath) end # Fetch the start line of the value. + #-- + #: () -> Integer def start_line fetch_value(:start_line) end # Fetch the end line of the value. + #-- + #: () -> Integer def end_line fetch_value(:end_line) end # Fetch the start byte offset of the value. + #-- + #: () -> Integer def start_offset fetch_value(:start_offset) end # Fetch the end byte offset of the value. + #-- + #: () -> Integer def end_offset fetch_value(:end_offset) end # Fetch the start character offset of the value. + #-- + #: () -> Integer def start_character_offset fetch_value(:start_character_offset) end # Fetch the end character offset of the value. + #-- + #: () -> Integer def end_character_offset fetch_value(:end_character_offset) end # Fetch the start code units offset of the value, for the encoding that # was configured on the repository. + #-- + #: () -> Integer def start_code_units_offset fetch_value(:start_code_units_offset) end # Fetch the end code units offset of the value, for the encoding that was # configured on the repository. + #-- + #: () -> Integer def end_code_units_offset fetch_value(:end_code_units_offset) end # Fetch the start byte column of the value. + #-- + #: () -> Integer def start_column fetch_value(:start_column) end # Fetch the end byte column of the value. + #-- + #: () -> Integer def end_column fetch_value(:end_column) end # Fetch the start character column of the value. + #-- + #: () -> Integer def start_character_column fetch_value(:start_character_column) end # Fetch the end character column of the value. + #-- + #: () -> Integer def end_character_column fetch_value(:end_character_column) end # Fetch the start code units column of the value, for the encoding that # was configured on the repository. + #-- + #: () -> Integer def start_code_units_column fetch_value(:start_code_units_column) end # Fetch the end code units column of the value, for the encoding that was # configured on the repository. + #-- + #: () -> Integer def end_code_units_column fetch_value(:end_code_units_column) end # Fetch the leading comments of the value. + #-- + #: () -> Array[CommentsField::Comment] def leading_comments fetch_value(:leading_comments) end # Fetch the trailing comments of the value. + #-- + #: () -> Array[CommentsField::Comment] def trailing_comments fetch_value(:trailing_comments) end # Fetch the leading and trailing comments of the value. + #-- + #: () -> Array[CommentsField::Comment] def comments - leading_comments.concat(trailing_comments) + [*leading_comments, *trailing_comments] end # Reify the values on this entry with the given values. This is an # internal-only API that is called from the repository when it is time to # reify the values. + #-- + #: (entry_values values) -> void def reify!(values) # :nodoc: @repository = nil @values = values @@ -132,6 +203,8 @@ def reify!(values) # :nodoc: private # Fetch a value from the entry, raising an error if it is missing. + #-- + #: (Symbol name) -> entry_value def fetch_value(name) values.fetch(name) do raise MissingValueError, "No value for #{name}, make sure the " \ @@ -140,27 +213,35 @@ def fetch_value(name) end # Return the values from the repository, reifying them if necessary. + #-- + #: () -> entry_values def values - @values || (@repository.reify!; @values) + @values || (@repository&.reify!; @values) #: entry_values end end # Represents the source of a repository that will be reparsed. class Source # The value that will need to be reparsed. - attr_reader :value + attr_reader :value #: untyped # Initialize the source with the given value. + #-- + #: (untyped value) -> void def initialize(value) @value = value end # Reparse the value and return the parse result. + #-- + #: () -> ParseResult def result raise NotImplementedError, "Subclasses must implement #result" end # Create a code units cache for the given encoding. + #-- + #: (Encoding encoding) -> _CodeUnitsCache def code_units_cache(encoding) result.code_units_cache(encoding) end @@ -169,6 +250,8 @@ def code_units_cache(encoding) # A source that is represented by a file path. class SourceFilepath < Source # Reparse the file and return the parse result. + #-- + #: () -> ParseResult def result Prism.parse_file(value) end @@ -177,6 +260,8 @@ def result # A source that is represented by a string. class SourceString < Source # Reparse the string and return the parse result. + #-- + #: () -> ParseResult def result Prism.parse(value) end @@ -185,14 +270,18 @@ def result # A field that represents the file path. class FilepathField # The file path that this field represents. - attr_reader :value + attr_reader :value #: String # Initialize a new field with the given file path. + #-- + #: (String value) -> void def initialize(value) @value = value end # Fetch the file path. + #-- + #: (_Value _value) -> entry_values def fields(_value) { filepath: value } end @@ -201,6 +290,8 @@ def fields(_value) # A field representing the start and end lines. class LinesField # Fetches the start and end line of a value. + #-- + #: (_Value value) -> entry_values def fields(value) { start_line: value.start_line, end_line: value.end_line } end @@ -209,6 +300,8 @@ def fields(value) # A field representing the start and end byte offsets. class OffsetsField # Fetches the start and end byte offset of a value. + #-- + #: (_Value value) -> entry_values def fields(value) { start_offset: value.start_offset, end_offset: value.end_offset } end @@ -217,6 +310,8 @@ def fields(value) # A field representing the start and end character offsets. class CharacterOffsetsField # Fetches the start and end character offset of a value. + #-- + #: (_Value value) -> entry_values def fields(value) { start_character_offset: value.start_character_offset, @@ -229,12 +324,16 @@ def fields(value) class CodeUnitOffsetsField # A pointer to the repository object that is used for lazily creating a # code units cache. - attr_reader :repository + attr_reader :repository #: Repository # The associated encoding for the code units. - attr_reader :encoding + attr_reader :encoding #: Encoding + + # @rbs @cache: _CodeUnitsCache? # Initialize a new field with the associated repository and encoding. + #-- + #: (Repository repository, Encoding encoding) -> void def initialize(repository, encoding) @repository = repository @encoding = encoding @@ -243,6 +342,8 @@ def initialize(repository, encoding) # Fetches the start and end code units offset of a value for a particular # encoding. + #-- + #: (_Value value) -> entry_values def fields(value) { start_code_units_offset: value.cached_start_code_units_offset(cache), @@ -253,6 +354,8 @@ def fields(value) private # Lazily create a code units cache for the associated encoding. + #-- + #: () -> _CodeUnitsCache def cache @cache ||= repository.code_units_cache(encoding) end @@ -261,6 +364,8 @@ def cache # A field representing the start and end byte columns. class ColumnsField # Fetches the start and end byte column of a value. + #-- + #: (_Value value) -> entry_values def fields(value) { start_column: value.start_column, end_column: value.end_column } end @@ -269,6 +374,8 @@ def fields(value) # A field representing the start and end character columns. class CharacterColumnsField # Fetches the start and end character column of a value. + #-- + #: (_Value value) -> entry_values def fields(value) { start_character_column: value.start_character_column, @@ -282,12 +389,16 @@ def fields(value) class CodeUnitColumnsField # The repository object that is used for lazily creating a code units # cache. - attr_reader :repository + attr_reader :repository #: Repository # The associated encoding for the code units. - attr_reader :encoding + attr_reader :encoding #: Encoding + + # @rbs @cache: _CodeUnitsCache? # Initialize a new field with the associated repository and encoding. + #-- + #: (Repository repository, Encoding encoding) -> void def initialize(repository, encoding) @repository = repository @encoding = encoding @@ -296,6 +407,8 @@ def initialize(repository, encoding) # Fetches the start and end code units column of a value for a particular # encoding. + #-- + #: (_Value value) -> entry_values def fields(value) { start_code_units_column: value.cached_start_code_units_column(cache), @@ -306,6 +419,8 @@ def fields(value) private # Lazily create a code units cache for the associated encoding. + #-- + #: () -> _CodeUnitsCache def cache @cache ||= repository.code_units_cache(encoding) end @@ -316,9 +431,11 @@ class CommentsField # An object that represents a slice of a comment. class Comment # The slice of the comment. - attr_reader :slice + attr_reader :slice #: String # Initialize a new comment with the given slice. + # + #: (String slice) -> void def initialize(slice) @slice = slice end @@ -327,6 +444,8 @@ def initialize(slice) private # Create comment objects from the given values. + #-- + #: (entry_value values) -> Array[Comment] def comments(values) values.map { |value| Comment.new(value.slice) } end @@ -335,6 +454,8 @@ def comments(values) # A field representing the leading comments. class LeadingCommentsField < CommentsField # Fetches the leading comments of a value. + #-- + #: (_Value value) -> entry_values def fields(value) { leading_comments: comments(value.leading_comments) } end @@ -343,6 +464,8 @@ def fields(value) # A field representing the trailing comments. class TrailingCommentsField < CommentsField # Fetches the trailing comments of a value. + #-- + #: (_Value value) -> entry_values def fields(value) { trailing_comments: comments(value.trailing_comments) } end @@ -358,15 +481,17 @@ class ConfigurationError < StandardError # The source associated with this repository. This will be either a # SourceFilepath (the most common use case) or a SourceString. - attr_reader :source + attr_reader :source #: Source # The fields that have been configured on this repository. - attr_reader :fields + attr_reader :fields #: Hash[Symbol, _Field] # The entries that have been saved on this repository. - attr_reader :entries + attr_reader :entries #: Hash[Integer, Hash[Symbol, Entry]] # Initialize a new repository with the given source. + #-- + #: (Source source) -> void def initialize(source) @source = source @fields = {} @@ -374,69 +499,93 @@ def initialize(source) end # Create a code units cache for the given encoding from the source. + #-- + #: (Encoding encoding) -> _CodeUnitsCache def code_units_cache(encoding) source.code_units_cache(encoding) end # Configure the filepath field for this repository and return self. + #-- + #: () -> self def filepath raise ConfigurationError, "Can only specify filepath for a filepath source" unless source.is_a?(SourceFilepath) field(:filepath, FilepathField.new(source.value)) end # Configure the lines field for this repository and return self. + #-- + #: () -> self def lines field(:lines, LinesField.new) end # Configure the offsets field for this repository and return self. + #-- + #: () -> self def offsets field(:offsets, OffsetsField.new) end # Configure the character offsets field for this repository and return # self. + #-- + #: () -> self def character_offsets field(:character_offsets, CharacterOffsetsField.new) end # Configure the code unit offsets field for this repository for a specific # encoding and return self. + #-- + #: (Encoding encoding) -> self def code_unit_offsets(encoding) field(:code_unit_offsets, CodeUnitOffsetsField.new(self, encoding)) end # Configure the columns field for this repository and return self. + #-- + #: () -> self def columns field(:columns, ColumnsField.new) end # Configure the character columns field for this repository and return # self. + #-- + #: () -> self def character_columns field(:character_columns, CharacterColumnsField.new) end # Configure the code unit columns field for this repository for a specific # encoding and return self. + #-- + #: (Encoding encoding) -> self def code_unit_columns(encoding) field(:code_unit_columns, CodeUnitColumnsField.new(self, encoding)) end # Configure the leading comments field for this repository and return # self. + #-- + #: () -> self def leading_comments field(:leading_comments, LeadingCommentsField.new) end # Configure the trailing comments field for this repository and return # self. + #-- + #: () -> self def trailing_comments field(:trailing_comments, TrailingCommentsField.new) end # Configure both the leading and trailing comment fields for this # repository and return self. + #-- + #: () -> self def comments leading_comments.trailing_comments end @@ -444,6 +593,8 @@ def comments # This method is called from nodes and locations when they want to enter # themselves into the repository. It it internal-only and meant to be # called from the #save* APIs. + #-- + #: (Integer node_id, Symbol field_name) -> Entry def enter(node_id, field_name) # :nodoc: entry = Entry.new(self) @entries[node_id][field_name] = entry @@ -453,6 +604,8 @@ def enter(node_id, field_name) # :nodoc: # This method is called from the entries in the repository when they need # to reify their values. It is internal-only and meant to be called from # the various value APIs. + #-- + #: () -> void def reify! # :nodoc: result = source.result @@ -466,7 +619,7 @@ def reify! # :nodoc: while (node = queue.shift) @entries[node.node_id].each do |field_name, entry| value = node.public_send(field_name) - values = {} #: Hash[Symbol, untyped] + values = {} #: entry_values fields.each_value do |field| values.merge!(field.fields(value)) @@ -485,6 +638,8 @@ def reify! # :nodoc: # Append the given field to the repository and return the repository so # that these calls can be chained. + #-- + #: (Symbol name, _Field) -> self def field(name, value) raise ConfigurationError, "Cannot specify multiple #{name} fields" if @fields.key?(name) @fields[name] = value @@ -493,11 +648,15 @@ def field(name, value) end # Create a new repository for the given filepath. + #-- + #: (String value) -> Repository def self.filepath(value) Repository.new(SourceFilepath.new(value)) end # Create a new repository for the given string. + #-- + #: (String value) -> Repository def self.string(value) Repository.new(SourceString.new(value)) end diff --git a/lib/prism/string_query.rb b/lib/prism/string_query.rb index 547f58d2fa6c92..c0dee63d3f9911 100644 --- a/lib/prism/string_query.rb +++ b/lib/prism/string_query.rb @@ -1,29 +1,43 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism # Query methods that allow categorizing strings based on their context for # where they could be valid in a Ruby syntax tree. class StringQuery + # @rbs! + # def self.local?: (String string) -> bool + # def self.constant?: (String string) -> bool + # def self.method_name?: (String string) -> bool + # The string that this query is wrapping. - attr_reader :string + attr_reader :string #: String # Initialize a new query with the given string. + #-- + #: (String string) -> void def initialize(string) @string = string end # Whether or not this string is a valid local variable name. + #-- + #: () -> bool def local? StringQuery.local?(string) end # Whether or not this string is a valid constant name. + #-- + #: () -> bool def constant? StringQuery.constant?(string) end # Whether or not this string is a valid method name. + #-- + #: () -> bool def method_name? StringQuery.method_name?(string) end diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index 57b57135bcc1f2..d1afa5d8c54a24 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# rbs_inline: enabled # :markup: markdown module Prism diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index f13eff6bbe2d90..2dd018627c2559 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -2,7 +2,6 @@ # :markup: markdown # typed: ignore -# module Prism module Translation case RUBY_VERSION diff --git a/prism/templates/lib/prism/compiler.rb.erb b/prism/templates/lib/prism/compiler.rb.erb index 031557a221de3c..16035b5456135b 100644 --- a/prism/templates/lib/prism/compiler.rb.erb +++ b/prism/templates/lib/prism/compiler.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + module Prism # A compiler is a visitor that returns the value of each node as it visits. # This is as opposed to a visitor which will only walk the tree. This can be @@ -18,22 +20,29 @@ module Prism # class Compiler < Visitor # Visit an individual node. + #-- + #: (node?) -> untyped def visit(node) # :nodoc: node&.accept(self) end # Visit a list of nodes. + #-- + #: (Array[node?]) -> untyped def visit_all(nodes) # :nodoc: nodes.map { |node| node&.accept(self) } end # Visit the child nodes of the given node. + #-- + #: (node) -> Array[untyped] def visit_child_nodes(node) # :nodoc: node.each_child_node.map { |node| node.accept(self) } end <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> + #: (<%= node.name %>) -> Array[untyped] def visit_<%= node.human %>(node) # :nodoc: node.each_child_node.map { |node| node.accept(self) } end diff --git a/prism/templates/lib/prism/dispatcher.rb.erb b/prism/templates/lib/prism/dispatcher.rb.erb index e4ca84db2421d2..f1bd80a7cdcf2b 100644 --- a/prism/templates/lib/prism/dispatcher.rb.erb +++ b/prism/templates/lib/prism/dispatcher.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + module Prism # The dispatcher class fires events for nodes that are found while walking an # AST to all registered listeners. It's useful for performing different types @@ -32,26 +34,35 @@ module Prism # dispatcher.dispatch_once(integer) # class Dispatcher < Visitor - # attr_reader listeners: Hash[Symbol, Array[Listener]] - attr_reader :listeners + # A hash mapping event names to arrays of listeners that should be notified + # when that event is fired. + attr_reader :listeners #: Hash[Symbol, Array[untyped]] # Initialize a new dispatcher. + #-- + #: () -> void def initialize @listeners = {} end # Register a listener for one or more events. + #-- + #: (untyped, *Symbol) -> void def register(listener, *events) register_events(listener, events) end # Register all public methods of a listener that match the pattern # `on__(enter|leave)`. + #-- + #: (untyped) -> void def register_public_methods(listener) register_events(listener, listener.public_methods(false).grep(/\Aon_.+_(?:enter|leave)\z/)) end # Register a listener for the given events. + #-- + #: (untyped, Array[Symbol]) -> void private def register_events(listener, events) # :nodoc: events.each { |event| (listeners[event] ||= []) << listener } end @@ -60,11 +71,14 @@ module Prism alias dispatch visit # Dispatches a single event for `node` to all registered listeners. + #-- + #: (node node) -> void def dispatch_once(node) node.accept(DispatchOnce.new(listeners)) end <%- nodes.each do |node| -%> + #: (<%= node.name %> node) -> void def visit_<%= node.human %>(node) # :nodoc: listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) } super @@ -73,14 +87,17 @@ module Prism <%- end -%> class DispatchOnce < Visitor # :nodoc: - attr_reader :listeners + attr_reader :listeners #: Hash[Symbol, Array[untyped]] + #: (Hash[Symbol, Array[untyped]] listeners) -> void def initialize(listeners) @listeners = listeners end <%- nodes.each do |node| -%> # Dispatch enter and leave events for <%= node.name %> nodes. + #-- + #: (<%= node.name %> node) -> void def visit_<%= node.human %>(node) listeners[:on_<%= node.human %>_enter]&.each { |listener| listener.on_<%= node.human %>_enter(node) } listeners[:on_<%= node.human %>_leave]&.each { |listener| listener.on_<%= node.human %>_leave(node) } diff --git a/prism/templates/lib/prism/dot_visitor.rb.erb b/prism/templates/lib/prism/dot_visitor.rb.erb index 13c53af0d430d8..3e37cf1a57b2e7 100644 --- a/prism/templates/lib/prism/dot_visitor.rb.erb +++ b/prism/templates/lib/prism/dot_visitor.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + require "cgi/escape" require "cgi/util" unless defined?(CGI::EscapeExt) @@ -6,14 +8,18 @@ module Prism # subtree into a graphviz dot graph. class DotVisitor < Visitor class Field # :nodoc: - attr_reader :name, :value, :port + attr_reader :name #: String + attr_reader :value #: String? + attr_reader :port #: bool + #: (String name, String? value, bool port) -> void def initialize(name, value, port) @name = name @value = value @port = port end + #: () -> String def to_dot if port "#{name}" @@ -24,17 +30,21 @@ module Prism end class Table # :nodoc: - attr_reader :name, :fields + attr_reader :name #: String + attr_reader :fields #: Array[Field] + #: (String name) -> void def initialize(name) @name = name @fields = [] end + #: (String name, ?String? value, ?port: bool) -> void def field(name, value = nil, port: false) fields << Field.new(name, value, port) end + #: () -> String def to_dot dot = <<~DOT @@ -50,26 +60,31 @@ module Prism end class Digraph # :nodoc: - attr_reader :nodes, :waypoints, :edges + attr_reader :nodes, :waypoints, :edges #: Array[String] + #: () -> void def initialize @nodes = [] @waypoints = [] @edges = [] end + #: (String value) -> void def node(value) nodes << value end + #: (String value) -> void def waypoint(value) waypoints << value end + #: (String value) -> void def edge(value) edges << value end + #: () -> String def to_dot <<~DOT digraph "Prism" { @@ -93,19 +108,24 @@ module Prism private_constant :Field, :Table, :Digraph # The digraph that is being built. - attr_reader :digraph + attr_reader :digraph #: Digraph # Initialize a new dot visitor. + #-- + #: () -> void def initialize @digraph = Digraph.new end # Convert this visitor into a graphviz dot graph string. + #-- + #: () -> String def to_dot digraph.to_dot end <%- nodes.each do |node| -%> + # (<%= node.name %>) -> void def visit_<%= node.human %>(node) # :nodoc: table = Table.new("<%= node.name %>") id = node_id(node) @@ -151,7 +171,7 @@ module Prism <%- end -%> <%- end -%> - digraph.nodes << <<~DOT + digraph.node(<<~DOT) #{id} [ label=<#{table.to_dot.gsub(/\n/, "\n ")}> ]; @@ -164,11 +184,15 @@ module Prism private # Generate a unique node ID for a node throughout the digraph. + #-- + #: (node) -> String def node_id(node) # :nodoc: "Node_#{node.object_id}" end # Inspect a location to display the start and end line and columns in bytes. + #-- + #: (Location) -> String def location_inspect(location) # :nodoc: "(#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column})" end @@ -176,6 +200,8 @@ module Prism # Inspect a node that has <%= flag.human %> flags to display the flags as a # comma-separated list. + #-- + #: (<%= nodes.filter_map { |node| node.name if node.flags == flag }.join(" | ") %> node) -> String def <%= flag.human %>_inspect(node) # :nodoc: flags = [] #: Array[String] <%- flag.values.each do |value| -%> diff --git a/prism/templates/lib/prism/dsl.rb.erb b/prism/templates/lib/prism/dsl.rb.erb index e16ebb71101921..de265a095b07c5 100644 --- a/prism/templates/lib/prism/dsl.rb.erb +++ b/prism/templates/lib/prism/dsl.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + module Prism # The DSL module provides a set of methods that can be used to create prism # nodes in a more concise manner. For example, instead of writing: @@ -56,17 +58,31 @@ module Prism extend self # Create a new Source object. + #-- + #: (String string) -> Source def source(string) Source.for(string) end # Create a new Location object. + #-- + #: (?source: Source, ?start_offset: Integer, ?length: Integer) -> Location def location(source: default_source, start_offset: 0, length: 0) Location.new(source, start_offset, length) end <%- nodes.each do |node| -%> + <%- + params = [ + ["source", "Source"], + ["node_id", "Integer"], + ["location", "Location"], + ["flags", "Integer"] + ].concat(node.fields.map { |field| [field.name, field.rbs_class] }) + -%> # Create a new <%= node.name %> node. + #-- + #: (<%= params.map { |(name, type)| "?#{name}: #{type}" }.join(", ") %>) -> <%= node.name %> def <%= node.human %>(<%= ["source: default_source", "node_id: 0", "location: default_location", "flags: 0", *node.fields.map { |field| case field when Prism::Template::NodeField @@ -100,6 +116,8 @@ module Prism <%- flags.each do |flag| -%> # Retrieve the value of one of the <%= flag.name %> flags. + #-- + #: (Symbol name) -> Integer def <%= flag.human.chomp("s") %>(name) case name <%- flag.values.each do |value| -%> @@ -114,18 +132,24 @@ module Prism # The default source object that gets attached to nodes and locations if no # source is specified. + #-- + #: () -> Source def default_source Source.for("") end # The default location object that gets attached to nodes if no location is # specified, which uses the given source. + #-- + #: () -> Location def default_location Location.new(default_source, 0, 0) end # The default node that gets attached to nodes if no node is specified for a # required node field. + #-- + #: (Source source, Location location) -> node def default_node(source, location) MissingNode.new(source, -1, location, 0) end diff --git a/prism/templates/lib/prism/inspect_visitor.rb.erb b/prism/templates/lib/prism/inspect_visitor.rb.erb index 9a33cb8110f076..9b15cb3d8abc60 100644 --- a/prism/templates/lib/prism/inspect_visitor.rb.erb +++ b/prism/templates/lib/prism/inspect_visitor.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + module Prism # This visitor is responsible for composing the strings that get returned by # the various #inspect methods defined on each of the nodes. @@ -7,8 +9,9 @@ module Prism # when we hit an element in that list. In this case, we have a special # command that replaces the subsequent indent with the given value. class Replace # :nodoc: - attr_reader :value + attr_reader :value #: String + #: (String value) -> void def initialize(value) @value = value end @@ -17,18 +20,25 @@ module Prism private_constant :Replace # The current prefix string. - attr_reader :indent # :nodoc: + # :stopdoc: + attr_reader :indent #: String + # :startdoc: # The list of commands that we need to execute in order to compose the # final string. - attr_reader :commands # :nodoc: + #: stopdoc: + attr_reader :commands #: Array[[String | node | Replace, String]] + # :startdoc: + #: (?String indent) -> void def initialize(indent = +"") # :nodoc: @indent = indent @commands = [] end # Compose an inspect string for the given node. + #-- + #: (node node) -> String def self.compose(node) visitor = new node.accept(visitor) @@ -36,6 +46,8 @@ module Prism end # Compose the final string. + #-- + #: () -> String def compose # :nodoc: buffer = +"" replace = nil @@ -65,6 +77,7 @@ module Prism end <%- nodes.each do |node| -%> + #: (<%= node.name %> node) -> void def visit_<%= node.human %>(node) # :nodoc: commands << [inspect_node(<%= node.name.inspect %>, node), indent] <%- (fields = [node.flags || Prism::Template::Flags.empty, *node.fields]).each_with_index do |field, index| -%> @@ -112,12 +125,16 @@ module Prism private # Compose a header for the given node. + #-- + #: (String name, node node) -> String def inspect_node(name, node) # :nodoc: location = node.location "@ #{name} (location: (#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}))\n" end # Compose a string representing the given inner location field. + #-- + #: (Location? location) -> String def inspect_location(location) # :nodoc: if location "(#{location.start_line},#{location.start_column})-(#{location.end_line},#{location.end_column}) = #{location.slice.inspect}" diff --git a/prism/templates/lib/prism/mutation_compiler.rb.erb b/prism/templates/lib/prism/mutation_compiler.rb.erb index b223860f2f6ff2..6fcbb25dcc90df 100644 --- a/prism/templates/lib/prism/mutation_compiler.rb.erb +++ b/prism/templates/lib/prism/mutation_compiler.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + module Prism # This visitor walks through the tree and copies each node as it is being # visited. This is useful for consumers that want to mutate the tree, as you @@ -5,6 +7,7 @@ module Prism class MutationCompiler < Compiler <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> + #: (<%= node.name %>) -> node? def visit_<%= node.human %>(node) # :nodoc: <%- fields = node.fields.select { |field| [Prism::Template::NodeField, Prism::Template::OptionalNodeField, Prism::Template::NodeListField].include?(field.class) } -%> <%- if fields.any? -%> diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 8c88529c664f3c..195200e4824d6d 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -1,26 +1,48 @@ -# :markup: markdown +# rbs_inline: enabled module Prism + # @rbs! + # interface _Repository + # def enter: (Integer node_id, Symbol field_name) -> Relocation::Entry + # end + # + # interface _Node + # def deconstruct: () -> Array[Prism::node?] + # def inspect: () -> String + # end + # + # type node = Node & _Node + # This represents a node in the tree. It is the parent class of all of the # various node types. class Node # A pointer to the source that this node was created from. - attr_reader :source # :nodoc: + # :stopdoc: + attr_reader :source #: Source private :source + # :startdoc: # A unique identifier for this node. This is used in a very specific # use case where you want to keep around a reference to a node without # having to keep around the syntax tree in memory. This unique identifier # will be consistent across multiple parses of the same source code. - attr_reader :node_id + attr_reader :node_id #: Integer + + # The location associated with this node. For lazily loading Location + # objects, we keep it as a packed integer until it is accessed. + # @rbs @location: Location | Integer # Save this node using a saved source so that it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry def save(repository) repository.enter(node_id, :itself) end # A Location instance that represents the location of this node in the # source. + #-- + #: () -> Location def location location = @location return location if location.is_a?(Location) @@ -28,6 +50,8 @@ module Prism end # Save the location using a saved source so that it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry def save_location(repository) repository.enter(node_id, :location) end @@ -38,22 +62,30 @@ module Prism # -------------------------------------------------------------------------- # Delegates to [`start_line`](rdoc-ref:Location#start_line) of the associated location object. + #-- + #: () -> Integer def start_line location.start_line end # Delegates to [`end_line`](rdoc-ref:Location#end_line) of the associated location object. + #-- + #: () -> Integer def end_line location.end_line end # Delegates to [`start_offset`](rdoc-ref:Location#start_offset) of the associated location object. + #-- + #: () -> Integer def start_offset location = @location location.is_a?(Location) ? location.start_offset : location >> 32 end # Delegates to [`end_offset`](rdoc-ref:Location#end_offset) of the associated location object. + #-- + #: () -> Integer def end_offset location = @location location.is_a?(Location) ? location.end_offset : ((location >> 32) + (location & 0xFFFFFFFF)) @@ -61,73 +93,99 @@ module Prism # Delegates to [`start_character_offset`](rdoc-ref:Location#start_character_offset) # of the associated location object. + #-- + #: () -> Integer def start_character_offset location.start_character_offset end # Delegates to [`end_character_offset`](rdoc-ref:Location#end_character_offset) # of the associated location object. + #-- + #: () -> Integer def end_character_offset location.end_character_offset end # Delegates to [`cached_start_code_units_offset`](rdoc-ref:Location#cached_start_code_units_offset) # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_start_code_units_offset(cache) location.cached_start_code_units_offset(cache) end # Delegates to [`cached_end_code_units_offset`](rdoc-ref:Location#cached_end_code_units_offset) # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_end_code_units_offset(cache) location.cached_end_code_units_offset(cache) end # Delegates to [`start_column`](rdoc-ref:Location#start_column) of the associated location object. + #-- + #: () -> Integer def start_column location.start_column end # Delegates to [`end_column`](rdoc-ref:Location#end_column) of the associated location object. + #-- + #: () -> Integer def end_column location.end_column end # Delegates to [`start_character_column`](rdoc-ref:Location#start_character_column) # of the associated location object. + #-- + #: () -> Integer def start_character_column location.start_character_column end # Delegates to [`end_character_column`](rdoc-ref:Location#end_character_column) # of the associated location object. + #-- + #: () -> Integer def end_character_column location.end_character_column end # Delegates to [`cached_start_code_units_column`](rdoc-ref:Location#cached_start_code_units_column) # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_start_code_units_column(cache) location.cached_start_code_units_column(cache) end # Delegates to [`cached_end_code_units_column`](rdoc-ref:Location#cached_end_code_units_column) # of the associated location object. + #-- + #: (_CodeUnitsCache cache) -> Integer def cached_end_code_units_column(cache) location.cached_end_code_units_column(cache) end # Delegates to [`leading_comments`](rdoc-ref:Location#leading_comments) of the associated location object. + #-- + #: () -> Array[Comment] def leading_comments location.leading_comments end # Delegates to [`trailing_comments`](rdoc-ref:Location#trailing_comments) of the associated location object. + #-- + #: () -> Array[Comment] def trailing_comments location.trailing_comments end # Delegates to [`comments`](rdoc-ref:Location#comments) of the associated location object. + #-- + #: () -> Array[Comment] def comments location.comments end @@ -135,6 +193,8 @@ module Prism # :section: # Returns all of the lines of the source code associated with this node. + #-- + #: () -> Array[String] def source_lines location.source_lines end @@ -144,6 +204,8 @@ module Prism alias script_lines source_lines # Slice the location of the node from the source. + #-- + #: () -> String def slice location.slice end @@ -151,27 +213,37 @@ module Prism # Slice the location of the node from the source, starting at the beginning # of the line that the location starts on, ending at the end of the line # that the location ends on. + #-- + #: () -> String def slice_lines location.slice_lines end # An bitset of flags for this node. There are certain flags that are common # for all nodes, and then some nodes have specific flags. - attr_reader :flags # :nodoc: + # :stopdoc: + attr_reader :flags #: Integer protected :flags + # :startdoc: # Returns true if the node has the newline flag set. + #-- + #: () -> bool def newline? flags.anybits?(NodeFlags::NEWLINE) end # Returns true if the node has the static literal flag set. + #-- + #: () -> bool def static_literal? flags.anybits?(NodeFlags::STATIC_LITERAL) end # Similar to inspect, but respects the current level of indentation given by # the pretty print object. + #-- + #: (PP q) -> void def pretty_print(q) # :nodoc: q.seplist(inspect.chomp.each_line, -> { q.breakable }) do |line| q.text(line.chomp) @@ -180,6 +252,8 @@ module Prism end # Convert this node into a graphviz dot graph string. + #-- + #: () -> String def to_dot # @type self: node DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot @@ -191,9 +265,11 @@ module Prism # # Important to note is that the column given to this method should be in # bytes, as opposed to characters or code units. + #-- + #: (Integer line, Integer column) -> Array[node] def tunnel(line, column) - queue = [self] #: Array[Prism::node] - result = [] #: Array[Prism::node] + queue = [self] #: Array[node] + result = [] #: Array[node] offset = source.byte_offset(line, column) while (node = queue.shift) @@ -215,9 +291,10 @@ module Prism # particular condition. # # node.breadth_first_search { |node| node.node_id == node_id } - # + #-- + #: () { (node) -> bool } -> node? def breadth_first_search(&block) - queue = [self] #: Array[Prism::node] + queue = [self] #: Array[node] while (node = queue.shift) return node if yield node @@ -233,7 +310,8 @@ module Prism # particular condition. # # node.breadth_first_search_all { |node| node.is_a?(Prism::CallNode) } - # + #-- + #: () { (node) -> bool } -> Array[node] def breadth_first_search_all(&block) queue = [self] #: Array[Prism::node] results = [] #: Array[Prism::node] @@ -250,6 +328,8 @@ module Prism # Returns a list of the fields that exist for this node class. Fields # describe the structure of the node. This kind of reflection is useful for # things like recursively visiting each node _and_ field in the tree. + #-- + #: () -> Array[Reflection::Field] def self.fields # This method should only be called on subclasses of Node, not Node # itself. @@ -265,12 +345,16 @@ module Prism # -------------------------------------------------------------------------- # Accepts a visitor and calls back into the specialized visit function. + #-- + #: (_Visitor visitor) -> untyped def accept(visitor) raise NoMethodError, "undefined method `accept' for #{inspect}" end # Returns an array of child nodes, including `nil`s in the place of optional # nodes that were not present. + #-- + #: () -> Array[node?] def child_nodes raise NoMethodError, "undefined method `child_nodes' for #{inspect}" end @@ -280,23 +364,32 @@ module Prism # With a block given, yields each child node. Without a block, returns # an enumerator that contains each child node. Excludes any `nil`s in # the place of optional nodes that were not present. + #-- + #: () { (node) -> void } -> void + #: () -> Enumerator[node, void] def each_child_node raise NoMethodError, "undefined method `each_child_node' for #{inspect}" end # Returns an array of child nodes, excluding any `nil`s in the place of # optional nodes that were not present. + #-- + #: () -> Array[node] def compact_child_nodes raise NoMethodError, "undefined method `compact_child_nodes' for #{inspect}" end # Returns an array of child nodes and locations that could potentially have # comments attached to them. + #-- + #: () -> Array[node | Location] def comment_targets raise NoMethodError, "undefined method `comment_targets' for #{inspect}" end # Returns a string representation of the node. + #-- + #: () -> String def inspect raise NoMethodError, "undefined method `inspect' for #{inspect}" end @@ -313,6 +406,8 @@ module Prism # it uses a single integer comparison, but also because if you're on CRuby # you can take advantage of the fact that case statements with all symbol # keys will use a jump table. + #-- + #: () -> Symbol def type raise NoMethodError, "undefined method `type' for #{inspect}" end @@ -321,6 +416,8 @@ module Prism # splitting on the type of the node without having to do a long === chain. # Note that like #type, it will still be slower than using == for a single # class, but should be faster in a case statement or an array comparison. + #-- + #: () -> Symbol def self.type raise NoMethodError, "undefined method `type' for #{inspect}" end @@ -331,7 +428,13 @@ module Prism #<%= line %> <%- end -%> class <%= node.name -%> < Node + <%- node.fields.each do |field| -%> + # @rbs @<%= field.name %>: <%= field.rbs_class %> + <%- end -%> + # Initialize a new <%= node.name %> node. + #-- + #: (Source source, Integer node_id, Location location, Integer flags, <%= node.fields.map { |field| "#{field.rbs_class} #{field.name}" }.join(", ") %>) -> void def initialize(<%= ["source", "node_id", "location", "flags", *node.fields.map(&:name)].join(", ") %>) @source = source @node_id = node_id @@ -357,11 +460,15 @@ module Prism # ---------------------------------------------------------------------------------- # See Node.accept. + #-- + #: (_Visitor visitor) -> untyped def accept(visitor) visitor.visit_<%= node.human %>(self) end # See Node.child_nodes. + #-- + #: () -> Array[node?] def child_nodes [<%= node.fields.map { |field| case field @@ -372,6 +479,9 @@ module Prism end # See Node.each_child_node. + #-- + #: () { (node) -> void } -> void + #: () -> Enumerator[node, void] def each_child_node return to_enum(:each_child_node) unless block_given? @@ -380,7 +490,7 @@ module Prism <%- when Prism::Template::NodeField -%> yield <%= field.name %> <%- when Prism::Template::OptionalNodeField -%> - yield <%= field.name %> if <%= field.name %> + if (<%= field.name %> = self.<%= field.name %>); yield <%= field.name %>; end <%- when Prism::Template::NodeListField -%> <%= field.name %>.each { |node| yield node } <%- end -%> @@ -388,6 +498,8 @@ module Prism end # See Node.compact_child_nodes. + #-- + #: () -> Array[node] def compact_child_nodes <%- if node.fields.any? { |field| field.is_a?(Prism::Template::OptionalNodeField) } -%> compact = [] #: Array[Prism::node] @@ -396,7 +508,7 @@ module Prism <%- when Prism::Template::NodeField -%> compact << <%= field.name %> <%- when Prism::Template::OptionalNodeField -%> - compact << <%= field.name %> if <%= field.name %> + if (<%= field.name %> = self.<%= field.name %>); compact << <%= field.name %>; end <%- when Prism::Template::NodeListField -%> compact.concat(<%= field.name %>) <%- end -%> @@ -413,6 +525,8 @@ module Prism end # See Node.comment_targets. + #-- + #: () -> Array[node | Location] def comment_targets [<%= node.fields.map { |field| case field @@ -426,26 +540,34 @@ module Prism # copy(**fields) -> <%= node.name %> # # Creates a copy of self with the given fields, using self as the template. + #-- + #: (?node_id: Integer, ?location: Location, ?flags: Integer, <%= node.fields.map { |field| "?#{field.name}: #{field.rbs_class}" }.join(", ") %>) -> <%= node.name %> def copy(<%= (["node_id", "location", "flags"] + node.fields.map(&:name)).map { |field| "#{field}: self.#{field}" }.join(", ") %>) <%= node.name %>.new(<%= ["source", "node_id", "location", "flags", *node.fields.map(&:name)].join(", ") %>) end alias deconstruct child_nodes + #: (Array[Symbol]? keys) -> Hash[Symbol, untyped] def deconstruct_keys(keys) # :nodoc: { <%= (["node_id: node_id", "location: location"] + node.fields.map { |field| "#{field.name}: #{field.name}" }).join(", ") %> } end # See `Node#type`. + #-- + #: () -> :<%= node.human %> def type :<%= node.human %> end # See `Node.type`. + #-- + #: () -> :<%= node.human %> def self.type :<%= node.human %> end + #: () -> String def inspect # :nodoc: InspectVisitor.compose(self) end @@ -456,6 +578,8 @@ module Prism <%- node_flags.values.each do |value| -%> # :category: Flags # <%= value.comment %> + #-- + #: () -> bool def <%= value.name.downcase %>? flags.anybits?(<%= node_flags.name %>::<%= value.name %>) end @@ -476,6 +600,8 @@ module Prism #<%= line %> <%- end -%> <%- end -%> + #-- + #: () -> Location def <%= field.name %> location = @<%= field.name %> return location if location.is_a?(Location) @@ -485,6 +611,8 @@ module Prism # :category: Repository # Save the <%= field.name %> location using the given saved source so that # it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry def save_<%= field.name %>(repository) repository.enter(node_id, :<%= field.name %>) end @@ -501,6 +629,8 @@ module Prism #<%= line %> <%- end -%> <%- end -%> + #-- + #: () -> Location? def <%= field.name %> location = @<%= field.name %> case location @@ -516,6 +646,8 @@ module Prism # :category: Repository # Save the <%= field.name %> location using the given saved source so that # it can be retrieved later. + #-- + #: (_Repository repository) -> Relocation::Entry? def save_<%= field.name %>(repository) repository.enter(node_id, :<%= field.name %>) unless @<%= field.name %>.nil? end @@ -530,6 +662,8 @@ module Prism #<%= line %> <%- end -%> <%- end -%> + #-- + #: () -> <%= field.rbs_class %> def <%= field.name %> @<%= field.name %> end @@ -547,6 +681,8 @@ module Prism # <%= field.name.delete_suffix("_loc") %> -> String # # Slice the location of <%= field.name %> from the source. + #-- + #: () -> String def <%= field.name.delete_suffix("_loc") %> <%= field.name %>.slice end @@ -558,6 +694,8 @@ module Prism # <%= field.name.delete_suffix("_loc") %> -> String | nil # # Slice the location of <%= field.name %> from the source. + #-- + #: () -> String? def <%= field.name.delete_suffix("_loc") %> <%= field.name %>&.slice end @@ -566,6 +704,7 @@ module Prism <%- end -%> # :section: + #: (untyped other) -> bool def ===(other) # :nodoc: other.is_a?(<%= node.name %>)<%= " &&" if (fields = [*node.flags, *node.fields]).any? %> <%- fields.each_with_index do |field, index| -%> diff --git a/prism/templates/lib/prism/reflection.rb.erb b/prism/templates/lib/prism/reflection.rb.erb index 6c8b2f4d257714..d5ace99259ba96 100644 --- a/prism/templates/lib/prism/reflection.rb.erb +++ b/prism/templates/lib/prism/reflection.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + module Prism # The Reflection module provides the ability to reflect on the structure of # the syntax tree itself, as opposed to looking at a single syntax tree. This @@ -7,9 +9,11 @@ module Prism # for all other field types. class Field # The name of the field. - attr_reader :name + attr_reader :name #: Symbol # Initializes the field with the given name. + #-- + #: (Symbol name) -> void def initialize(name) @name = name end @@ -83,9 +87,11 @@ module Prism # the bitset should be accessed through their query methods. class FlagsField < Field # The names of the flags in the bitset. - attr_reader :flags + attr_reader :flags #: Array[Symbol] # Initializes the flags field with the given name and flags. + #-- + #: (Symbol name, Array[Symbol] flags) -> void def initialize(name, flags) super(name) @flags = flags @@ -93,6 +99,8 @@ module Prism end # Returns the fields for the given node. + #-- + #: (singleton(Node) node) -> Array[Field] def self.fields_for(node) case node.type <%- nodes.each do |node| -%> diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 63ef07cb6e9dbd..229322291e42c6 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -1,3 +1,5 @@ +# rbs_inline: enabled + require "stringio" require_relative "polyfill/unpack1" @@ -20,6 +22,8 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> ParseResult def self.load_parse(input, serialized, freeze) input = input.dup source = Source.for(input) @@ -43,7 +47,7 @@ module Prism constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size) - node = loader.load_node(constant_pool, encoding, freeze) + node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode loader.load_constant_pool(constant_pool) raise unless loader.eof? @@ -73,6 +77,8 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> LexResult def self.load_lex(input, serialized, freeze) source = Source.for(input) loader = Loader.new(source, serialized) @@ -117,6 +123,8 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> Array[Comment] def self.load_parse_comments(input, serialized, freeze) source = Source.for(input) loader = Loader.new(source, serialized) @@ -139,6 +147,8 @@ module Prism # # The formatting of the source of this method is purposeful to illustrate # the structure of the serialized data. + #-- + #: (String input, String serialized, bool freeze) -> ParseLexResult def self.load_parse_lex(input, serialized, freeze) source = Source.for(input) loader = Loader.new(source, serialized) @@ -162,11 +172,11 @@ module Prism constant_pool = ConstantPool.new(input, serialized, cpool_base, cpool_size) - node = loader.load_node(constant_pool, encoding, freeze) + node = loader.load_node(constant_pool, encoding, freeze) #: ProgramNode loader.load_constant_pool(constant_pool) raise unless loader.eof? - value = [node, tokens] + value = [node, tokens] #: [ProgramNode, Array[[Token, Integer]]] result = ParseLexResult.new(value, comments, magic_comments, data_loc, errors, warnings, source) tokens.each do |token| @@ -189,8 +199,14 @@ module Prism end class ConstantPool # :nodoc: - attr_reader :size + attr_reader :size #: Integer + + # @rbs @input: String + # @rbs @serialized: String + # @rbs @base: Integer + # @rbs @pool: Array[Symbol?] + #: (String input, String serialized, Integer base, Integer size) -> void def initialize(input, serialized, base, size) @input = input @serialized = serialized @@ -199,17 +215,18 @@ module Prism @pool = Array.new(size, nil) end + #: (Integer index, Encoding encoding) -> Symbol def get(index, encoding) @pool[index] ||= begin offset = @base + index * 8 - start = @serialized.unpack1("L", offset: offset) - length = @serialized.unpack1("L", offset: offset + 4) + start = @serialized.unpack1("L", offset: offset) #: Integer + length = @serialized.unpack1("L", offset: offset + 4) #: Integer if start.nobits?(1 << 31) - @input.byteslice(start, length).force_encoding(encoding).to_sym + (@input.byteslice(start, length) or raise).force_encoding(encoding).to_sym else - @serialized.byteslice(start & ((1 << 31) - 1), length).force_encoding(encoding).to_sym + (@serialized.byteslice(start & ((1 << 31) - 1), length) or raise).force_encoding(encoding).to_sym end end end @@ -217,6 +234,7 @@ module Prism if RUBY_ENGINE == "truffleruby" # StringIO is synchronized and that adds a high overhead on TruffleRuby. + # @rbs skip class FastStringIO # :nodoc: attr_accessor :pos @@ -246,8 +264,11 @@ module Prism end class Loader # :nodoc: - attr_reader :input, :io, :source + attr_reader :input #: String + attr_reader :io #: StringIO + attr_reader :source #: Source + #: (Source source, String serialized) -> void def initialize(source, serialized) @input = source.source.dup raise unless serialized.encoding == Encoding::BINARY @@ -256,40 +277,46 @@ module Prism define_load_node_lambdas if RUBY_ENGINE != "ruby" end + #: () -> bool def eof? io.getbyte io.eof? end + #: (ConstantPool constant_pool) -> void def load_constant_pool(constant_pool) trailer = 0 constant_pool.size.times do |index| - start, length = io.read(8).unpack("L2") + start, length = (io.read(8) or raise).unpack("L2") #: [Integer, Integer] trailer += length if start.anybits?(1 << 31) end io.read(trailer) end + #: () -> void def load_header raise "Invalid serialization" if io.read(5) != "PRISM" - raise "Invalid serialization" if io.read(3).unpack("C3") != [MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION] + raise "Invalid serialization" if (io.read(3) or raise).unpack("C3") != [MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION] raise "Invalid serialization (location fields must be included but are not)" if io.getbyte != 0 end + #: () -> Encoding def load_encoding - encoding = Encoding.find(io.read(load_varuint)) + encoding = Encoding.find((io.read(load_varuint) or raise)) or raise @input = input.force_encoding(encoding).freeze encoding end + #: (bool freeze) -> Array[Integer] def load_line_offsets(freeze) offsets = Array.new(load_varuint) { load_varuint } offsets.freeze if freeze offsets end + #: (bool freeze) -> Array[Comment] def load_comments(freeze) comments = Array.new(load_varuint) do @@ -297,6 +324,7 @@ module Prism case load_varuint when 0 then InlineComment.new(load_location_object(freeze)) when 1 then EmbDocComment.new(load_location_object(freeze)) + else raise end comment.freeze if freeze @@ -307,6 +335,7 @@ module Prism comments end + #: (bool freeze) -> Array[MagicComment] def load_magic_comments(freeze) magic_comments = Array.new(load_varuint) do @@ -331,10 +360,11 @@ module Prism <%- warnings.each do |warning| -%> <%= warning.name.downcase.to_sym.inspect %>, <%- end -%> - ].freeze + ].freeze #: Array[Symbol] private_constant :DIAGNOSTIC_TYPES + #: () -> Symbol def load_error_level level = io.getbyte @@ -350,6 +380,7 @@ module Prism end end + #: (Encoding encoding, bool freeze) -> Array[ParseError] def load_errors(encoding, freeze) errors = Array.new(load_varuint) do @@ -369,6 +400,7 @@ module Prism errors end + #: () -> Symbol def load_warning_level level = io.getbyte @@ -382,6 +414,7 @@ module Prism end end + #: (Encoding encoding, bool freeze) -> Array[ParseWarning] def load_warnings(encoding, freeze) warnings = Array.new(load_varuint) do @@ -401,8 +434,9 @@ module Prism warnings end + #: () -> Array[[Token, Integer]] def load_tokens - tokens = [] + tokens = [] #: Array[[Token, Integer]] while (type = TOKEN_TYPES.fetch(load_varuint)) start = load_varuint @@ -420,25 +454,29 @@ module Prism # variable-length integer using https://en.wikipedia.org/wiki/LEB128 # This is also what protobuf uses: https://protobuf.dev/programming-guides/encoding/#varints + #-- + #: () -> Integer def load_varuint - n = io.getbyte + n = (io.getbyte or raise) if n < 128 n else n -= 128 shift = 0 - while (b = io.getbyte) >= 128 + while (b = (io.getbyte or raise)) >= 128 n += (b - 128) << (shift += 7) end n + (b << (shift + 7)) end end + #: () -> Integer def load_varsint n = load_varuint (n >> 1) ^ (-(n & 1)) end + #: () -> Integer def load_integer negative = io.getbyte != 0 length = load_varuint @@ -450,14 +488,17 @@ module Prism value end + #: () -> Float def load_double - io.read(8).unpack1("D") + (io.read(8) or raise).unpack1("D") #: Float end + #: () -> Integer def load_uint32 - io.read(4).unpack1("L") + (io.read(4) or raise).unpack1("L") #: Integer end + #: (ConstantPool constant_pool, Encoding encoding, bool freeze) -> node? def load_optional_node(constant_pool, encoding, freeze) if io.getbyte != 0 io.pos -= 1 @@ -465,14 +506,16 @@ module Prism end end + #: (Encoding encoding) -> String def load_embedded_string(encoding) - io.read(load_varuint).force_encoding(encoding).freeze + (io.read(load_varuint) or raise).force_encoding(encoding).freeze end + #: (Encoding encoding) -> String def load_string(encoding) case (type = io.getbyte) when 1 - input.byteslice(load_varuint, load_varuint).force_encoding(encoding).freeze + (input.byteslice(load_varuint, load_varuint) or raise).force_encoding(encoding).freeze when 2 load_embedded_string(encoding) else @@ -480,75 +523,116 @@ module Prism end end + #: (bool freeze) -> Location def load_location_object(freeze) location = Location.new(source, load_varuint, load_varuint) location.freeze if freeze location end + # Load a location object from the serialized data. Note that we are lying + # about the signature a bit here, because we sometimes load it as a packed + # integer instead of an object. + #-- + #: (bool freeze) -> Location def load_location(freeze) return load_location_object(freeze) if freeze - (load_varuint << 32) | load_varuint + (load_varuint << 32) | load_varuint #: Location end + # Load an optional location object from the serialized data if it is + # present. Note that we are lying about the signature a bit here, because + # we sometimes load it as a packed integer instead of an object. + #-- + #: (bool freeze) -> Location? def load_optional_location(freeze) load_location(freeze) if io.getbyte != 0 end + #: (bool freeze) -> Location? def load_optional_location_object(freeze) load_location_object(freeze) if io.getbyte != 0 end + #: (ConstantPool constant_pool, Encoding encoding) -> Symbol def load_constant(constant_pool, encoding) index = load_varuint constant_pool.get(index - 1, encoding) end + #: (ConstantPool constant_pool, Encoding encoding) -> Symbol? def load_optional_constant(constant_pool, encoding) index = load_varuint constant_pool.get(index - 1, encoding) if index != 0 end if RUBY_ENGINE == "ruby" + #: (ConstantPool constant_pool, Encoding encoding, bool freeze) -> node def load_node(constant_pool, encoding, freeze) type = io.getbyte node_id = load_varuint - location = load_location(freeze) - value = case type - <%- nodes.each_with_index do |node, index| -%> - when <%= index + 1 %> then - <%- if node.needs_serialized_length? -%> - load_uint32 - <%- end -%> - <%= node.name %>.new(<%= ["source", "node_id", "location", "load_varuint", *node.fields.map { |field| - case field - when Prism::Template::NodeField then "load_node(constant_pool, encoding, freeze)" - when Prism::Template::OptionalNodeField then "load_optional_node(constant_pool, encoding, freeze)" - when Prism::Template::StringField then "load_string(encoding)" - when Prism::Template::NodeListField then "Array.new(load_varuint) { load_node(constant_pool, encoding, freeze) }.tap { |nodes| nodes.freeze if freeze }" - when Prism::Template::ConstantField then "load_constant(constant_pool, encoding)" - when Prism::Template::OptionalConstantField then "load_optional_constant(constant_pool, encoding)" - when Prism::Template::ConstantListField then "Array.new(load_varuint) { load_constant(constant_pool, encoding) }.tap { |constants| constants.freeze if freeze }" - when Prism::Template::LocationField then "load_location(freeze)" - when Prism::Template::OptionalLocationField then "load_optional_location(freeze)" - when Prism::Template::UInt8Field then "io.getbyte" - when Prism::Template::UInt32Field then "load_varuint" - when Prism::Template::IntegerField then "load_integer" - when Prism::Template::DoubleField then "load_double" - else raise - end - }].join(", ") -%>) + location = load_location(freeze) #: Location + value = + case type + <%- nodes.each_with_index do |node, index| -%> + when <%= index + 1 %> + <%- if node.needs_serialized_length? -%> + load_uint32 + <%- end -%> + <%= node.name %>.new( + source, + node_id, + location, + load_varuint, + <%- node.fields.each do |field| -%> + <%- case field -%> + <%- when Prism::Template::NodeField -%> + load_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::OptionalNodeField -%> + load_optional_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::StringField -%> + load_string(encoding), + <%- when Prism::Template::NodeListField -%> + Array.new(load_varuint) do + load_node(constant_pool, encoding, freeze) #: <%= field.element_rbs_class %> + end.tap { |nodes| nodes.freeze if freeze }, + <%- when Prism::Template::ConstantField -%> + load_constant(constant_pool, encoding), + <%- when Prism::Template::OptionalConstantField -%> + load_optional_constant(constant_pool, encoding), + <%- when Prism::Template::ConstantListField -%> + Array.new(load_varuint) { load_constant(constant_pool, encoding) }.tap { |constants| constants.freeze if freeze }, + <%- when Prism::Template::LocationField -%> + load_location(freeze), + <%- when Prism::Template::OptionalLocationField -%> + load_optional_location(freeze), + <%- when Prism::Template::UInt8Field -%> + (io.getbyte or raise), + <%- when Prism::Template::UInt32Field -%> + load_varuint, + <%- when Prism::Template::IntegerField -%> + load_integer, + <%- when Prism::Template::DoubleField -%> + load_double, + <%- else raise -%> + <%- end -%> + <%- end -%> + ) <%- end -%> - end + else + raise "Unknown node type: #{type}" + end value.freeze if freeze value end else + # @rbs skip def load_node(constant_pool, encoding, freeze) - @load_node_lambdas[io.getbyte].call(constant_pool, encoding, freeze) + @load_node_lambdas[(io.getbyte or raise)].call(constant_pool, encoding, freeze) end + # @rbs skip def define_load_node_lambdas @load_node_lambdas = [ nil, @@ -559,24 +643,46 @@ module Prism <%- if node.needs_serialized_length? -%> load_uint32 <%- end -%> - value = <%= node.name %>.new(<%= ["source", "node_id", "location", "load_varuint", *node.fields.map { |field| - case field - when Prism::Template::NodeField then "load_node(constant_pool, encoding, freeze)" - when Prism::Template::OptionalNodeField then "load_optional_node(constant_pool, encoding, freeze)" - when Prism::Template::StringField then "load_string(encoding)" - when Prism::Template::NodeListField then "Array.new(load_varuint) { load_node(constant_pool, encoding, freeze) }" - when Prism::Template::ConstantField then "load_constant(constant_pool, encoding)" - when Prism::Template::OptionalConstantField then "load_optional_constant(constant_pool, encoding)" - when Prism::Template::ConstantListField then "Array.new(load_varuint) { load_constant(constant_pool, encoding) }" - when Prism::Template::LocationField then "load_location(freeze)" - when Prism::Template::OptionalLocationField then "load_optional_location(freeze)" - when Prism::Template::UInt8Field then "io.getbyte" - when Prism::Template::UInt32Field then "load_varuint" - when Prism::Template::IntegerField then "load_integer" - when Prism::Template::DoubleField then "load_double" - else raise - end - }].join(", ") -%>) + value = + <%= node.name %>.new( + source, + node_id, + location, + load_varuint, + <%- node.fields.map do |field| -%> + <%- case field -%> + <%- when Prism::Template::NodeField -%> + load_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::OptionalNodeField -%> + load_optional_node(constant_pool, encoding, freeze), #: <%= field.rbs_class %> + <%- when Prism::Template::StringField -%> + load_string(encoding), + <%- when Prism::Template::NodeListField -%> + Array.new(load_varuint) do + load_node(constant_pool, encoding, freeze) #: <%= field.element_rbs_class %> + end, + <%- when Prism::Template::ConstantField -%> + load_constant(constant_pool, encoding), + <%- when Prism::Template::OptionalConstantField -%> + load_optional_constant(constant_pool, encoding), + <%- when Prism::Template::ConstantListField -%> + Array.new(load_varuint) { load_constant(constant_pool, encoding) }, + <%- when Prism::Template::LocationField -%> + load_location(freeze), + <%- when Prism::Template::OptionalLocationField -%> + load_optional_location(freeze), + <%- when Prism::Template::UInt8Field -%> + (io.getbyte or raise), + <%- when Prism::Template::UInt32Field -%> + load_varuint, + <%- when Prism::Template::IntegerField -%> + load_integer, + <%- when Prism::Template::DoubleField -%> + load_double, + <%- else raise -%> + <%- end -%> + <%- end -%> + ) value.freeze if freeze value }, @@ -584,6 +690,10 @@ module Prism ] end end + + # @rbs! + # @load_node_lambdas: Array[Proc] + # def define_load_node_lambdas: () -> void end # The token types that can be indexed by their enum values. @@ -592,7 +702,7 @@ module Prism <%- tokens.each do |token| -%> <%= token.name.to_sym.inspect %>, <%- end -%> - ].freeze + ].freeze #: Array[Symbol?] private_constant :MAJOR_VERSION, :MINOR_VERSION, :PATCH_VERSION private_constant :ConstantPool, :FastStringIO, :Loader, :TOKEN_TYPES diff --git a/prism/templates/lib/prism/visitor.rb.erb b/prism/templates/lib/prism/visitor.rb.erb index 76f907724fd5da..78726c53f1324e 100644 --- a/prism/templates/lib/prism/visitor.rb.erb +++ b/prism/templates/lib/prism/visitor.rb.erb @@ -1,4 +1,13 @@ +# rbs_inline: enabled + module Prism + # @rbs! + # interface _Visitor + # <% nodes.each do |node| %> + # def visit_<%= node.human %>: (<%= node.name %>) -> void + # <% end %> + # end + # A class that knows how to walk down the tree. None of the individual visit # methods are implemented on this visitor, so it forces the consumer to # implement each one that they need. For a default implementation that @@ -6,18 +15,24 @@ module Prism class BasicVisitor # Calls `accept` on the given node if it is not `nil`, which in turn should # call back into this visitor by calling the appropriate `visit_*` method. + #-- + #: (node? node) -> void def visit(node) # @type self: _Visitor node&.accept(self) end # Visits each node in `nodes` by calling `accept` on each one. + #-- + #: (Array[node?] nodes) -> void def visit_all(nodes) # @type self: _Visitor nodes.each { |node| node&.accept(self) } end # Visits the child nodes of `node` by calling `accept` on each one. + #-- + #: (node node) -> void def visit_child_nodes(node) # @type self: _Visitor node.each_child_node { |node| node.accept(self) } @@ -47,6 +62,8 @@ module Prism <%- nodes.each_with_index do |node, index| -%> <%= "\n" if index != 0 -%> # Visit a <%= node.name %> node + #-- + #: (<%= node.name %> node) -> void def visit_<%= node.human %>(node) node.each_child_node { |node| node.accept(self) } end diff --git a/prism/templates/template.rb b/prism/templates/template.rb index 18da0647a007bd..a39f8bbb784297 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -150,7 +150,7 @@ def rbs_class if specific_kind specific_kind elsif union_kind - union_kind.join(" | ") + "(#{union_kind.join(" | ")})" else "Prism::node" end @@ -192,7 +192,7 @@ def rbs_class if specific_kind "#{specific_kind}?" elsif union_kind - [*union_kind, "nil"].join(" | ") + "(#{union_kind.join(" | ")})?" else "Prism::node?" end @@ -230,16 +230,20 @@ def check_field_kind # This represents a field on a node that is a list of nodes. We pass them as # references and store them directly on the struct. class NodeListField < NodeKindField - def rbs_class + def element_rbs_class if specific_kind - "Array[#{specific_kind}]" + "#{specific_kind}" elsif union_kind - "Array[#{union_kind.join(" | ")}]" + "#{union_kind.join(" | ")}" else - "Array[Prism::node]" + "Prism::node" end end + def rbs_class + "Array[#{element_rbs_class}]" + end + def call_seq_type if specific_kind "Array[#{specific_kind}]" @@ -639,13 +643,6 @@ def render(name, write_to: nil) ++ =end - HEADING - when ".rbs" - <<~HEADING - # This file is generated by the templates/template.rb script and should not be - # modified manually. See #{filepath} - # if you are looking to modify the template - HEADING when ".rbi" <<~HEADING @@ -744,13 +741,7 @@ def locals "src/token_type.c", "rbi/prism/dsl.rbi", "rbi/prism/node.rbi", - "rbi/prism/visitor.rbi", - "sig/prism.rbs", - "sig/prism/dsl.rbs", - "sig/prism/mutation_compiler.rbs", - "sig/prism/node.rbs", - "sig/prism/visitor.rbs", - "sig/prism/_private/dot_visitor.rbs" + "rbi/prism/visitor.rbi" ] end end From bc1d75adced050f01248a96ba90ded345ffc08db Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:05:31 +0100 Subject: [PATCH 22/35] [ruby/prism] Don't document rbs inline magic comment https://github.com/ruby/prism/commit/e371985c91 --- lib/prism.rb | 3 ++- lib/prism/desugar_compiler.rb | 3 ++- lib/prism/lex_compat.rb | 3 ++- lib/prism/node_ext.rb | 3 ++- lib/prism/parse_result.rb | 3 ++- lib/prism/parse_result/comments.rb | 3 ++- lib/prism/parse_result/errors.rb | 3 ++- lib/prism/parse_result/newlines.rb | 3 ++- lib/prism/pattern.rb | 3 ++- lib/prism/relocation.rb | 3 ++- lib/prism/string_query.rb | 3 ++- lib/prism/translation.rb | 3 ++- lib/prism/translation/parser_current.rb | 1 + prism/templates/lib/prism/compiler.rb.erb | 1 + prism/templates/lib/prism/dispatcher.rb.erb | 1 + prism/templates/lib/prism/dot_visitor.rb.erb | 1 + prism/templates/lib/prism/dsl.rb.erb | 1 + prism/templates/lib/prism/inspect_visitor.rb.erb | 1 + prism/templates/lib/prism/mutation_compiler.rb.erb | 1 + prism/templates/lib/prism/node.rb.erb | 1 + prism/templates/lib/prism/reflection.rb.erb | 1 + prism/templates/lib/prism/serialize.rb.erb | 1 + prism/templates/lib/prism/visitor.rb.erb | 1 + 23 files changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/prism.rb b/lib/prism.rb index c7530e5874045d..079026cf89f5e4 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled # The Prism Ruby parser. # diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb index e08faa321919b0..c64d03f64ae1ec 100644 --- a/lib/prism/desugar_compiler.rb +++ b/lib/prism/desugar_compiler.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism class DesugarAndWriteNode # :nodoc: diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index bdef99acfc9535..7f3f30b88da7f8 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism # @rbs! diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index 57593a16125cd7..61f36e09bed8ef 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled #-- # Here we are reopening the prism module to provide methods on nodes that aren't diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index e2e1145bdc60b9..9825d559aff9ba 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism # @rbs! diff --git a/lib/prism/parse_result/comments.rb b/lib/prism/parse_result/comments.rb index 7b54ce7fd65386..df80792d39cd0e 100644 --- a/lib/prism/parse_result/comments.rb +++ b/lib/prism/parse_result/comments.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism class ParseResult < Result diff --git a/lib/prism/parse_result/errors.rb b/lib/prism/parse_result/errors.rb index 03d65daecf0552..388309d23d2297 100644 --- a/lib/prism/parse_result/errors.rb +++ b/lib/prism/parse_result/errors.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled require "stringio" diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb index cfbc1ea1d0e97f..82e72bd564102e 100644 --- a/lib/prism/parse_result/newlines.rb +++ b/lib/prism/parse_result/newlines.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism class ParseResult < Result diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb index b15b04d9bc97c0..cbfb2c69b7267a 100644 --- a/lib/prism/pattern.rb +++ b/lib/prism/pattern.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism # A pattern is an object that wraps a Ruby pattern matching expression. The diff --git a/lib/prism/relocation.rb b/lib/prism/relocation.rb index 2ac471d4250ebc..af0f7928272d8e 100644 --- a/lib/prism/relocation.rb +++ b/lib/prism/relocation.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism # Prism parses deterministically for the same input. This provides a nice diff --git a/lib/prism/string_query.rb b/lib/prism/string_query.rb index c0dee63d3f9911..99ce57e5fec4c6 100644 --- a/lib/prism/string_query.rb +++ b/lib/prism/string_query.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism # Query methods that allow categorizing strings based on their context for diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index d1afa5d8c54a24..5a086a75423443 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -# rbs_inline: enabled # :markup: markdown +#-- +# rbs_inline: enabled module Prism # This module is responsible for converting the prism syntax tree into other diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb index 2dd018627c2559..f7c1070e30c80e 100644 --- a/lib/prism/translation/parser_current.rb +++ b/lib/prism/translation/parser_current.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true # :markup: markdown +#-- # typed: ignore module Prism diff --git a/prism/templates/lib/prism/compiler.rb.erb b/prism/templates/lib/prism/compiler.rb.erb index 16035b5456135b..13317cac0449e2 100644 --- a/prism/templates/lib/prism/compiler.rb.erb +++ b/prism/templates/lib/prism/compiler.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism diff --git a/prism/templates/lib/prism/dispatcher.rb.erb b/prism/templates/lib/prism/dispatcher.rb.erb index f1bd80a7cdcf2b..5991b0c9045bed 100644 --- a/prism/templates/lib/prism/dispatcher.rb.erb +++ b/prism/templates/lib/prism/dispatcher.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism diff --git a/prism/templates/lib/prism/dot_visitor.rb.erb b/prism/templates/lib/prism/dot_visitor.rb.erb index 3e37cf1a57b2e7..bdac9cfd3c6c0d 100644 --- a/prism/templates/lib/prism/dot_visitor.rb.erb +++ b/prism/templates/lib/prism/dot_visitor.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled require "cgi/escape" diff --git a/prism/templates/lib/prism/dsl.rb.erb b/prism/templates/lib/prism/dsl.rb.erb index de265a095b07c5..a75b8b253e240b 100644 --- a/prism/templates/lib/prism/dsl.rb.erb +++ b/prism/templates/lib/prism/dsl.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism diff --git a/prism/templates/lib/prism/inspect_visitor.rb.erb b/prism/templates/lib/prism/inspect_visitor.rb.erb index 9b15cb3d8abc60..820f5ae75fd7ee 100644 --- a/prism/templates/lib/prism/inspect_visitor.rb.erb +++ b/prism/templates/lib/prism/inspect_visitor.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism diff --git a/prism/templates/lib/prism/mutation_compiler.rb.erb b/prism/templates/lib/prism/mutation_compiler.rb.erb index 6fcbb25dcc90df..2d555048d2e102 100644 --- a/prism/templates/lib/prism/mutation_compiler.rb.erb +++ b/prism/templates/lib/prism/mutation_compiler.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 195200e4824d6d..2e42eeea13104d 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism diff --git a/prism/templates/lib/prism/reflection.rb.erb b/prism/templates/lib/prism/reflection.rb.erb index d5ace99259ba96..0012f120b2bf01 100644 --- a/prism/templates/lib/prism/reflection.rb.erb +++ b/prism/templates/lib/prism/reflection.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism diff --git a/prism/templates/lib/prism/serialize.rb.erb b/prism/templates/lib/prism/serialize.rb.erb index 229322291e42c6..433b5207883e2e 100644 --- a/prism/templates/lib/prism/serialize.rb.erb +++ b/prism/templates/lib/prism/serialize.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled require "stringio" diff --git a/prism/templates/lib/prism/visitor.rb.erb b/prism/templates/lib/prism/visitor.rb.erb index 78726c53f1324e..f23e87d99ec9b2 100644 --- a/prism/templates/lib/prism/visitor.rb.erb +++ b/prism/templates/lib/prism/visitor.rb.erb @@ -1,3 +1,4 @@ +#-- # rbs_inline: enabled module Prism From 027131973fe4e7fc11f87f930f5c2a4719d9d66d Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Sun, 22 Feb 2026 16:40:04 -0500 Subject: [PATCH 23/35] [ruby/prism] Generate RBI from RBS https://github.com/ruby/prism/commit/2c88ed893f --- lib/prism.rb | 4 +- lib/prism/lex_compat.rb | 9 ++ lib/prism/node_ext.rb | 3 +- lib/prism/parse_result/newlines.rb | 24 +++--- lib/prism/pattern.rb | 4 +- lib/prism/prism.gemspec | 32 ++++--- prism/templates/lib/prism/dot_visitor.rb.erb | 2 +- prism/templates/lib/prism/node.rb.erb | 14 +-- prism/templates/template.rb | 89 +------------------- 9 files changed, 58 insertions(+), 123 deletions(-) diff --git a/lib/prism.rb b/lib/prism.rb index 079026cf89f5e4..6b34ab12bfc6e5 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -43,12 +43,12 @@ class CurrentVersionError < ArgumentError #: (String version) -> void def initialize(version) message = +"invalid version: Requested to parse as `version: 'current'`; " - segments = + major, minor, = if version.match?(/\A\d+\.\d+.\d+\z/) version.split(".").map(&:to_i) end - if segments && ((segments[0] < 3) || (segments[0] == 3 && segments[1] < 3)) + if major && minor && ((major < 3) || (major == 3 && minor < 3)) message << " #{version} is below the minimum supported syntax." else message << " #{version} is unknown. Please update the `prism` gem." diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index 7f3f30b88da7f8..99d8daacdd26cf 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -7,7 +7,16 @@ module Prism # @rbs! # module Translation # class Ripper + # EXPR_NONE: Integer # EXPR_BEG: Integer + # EXPR_MID: Integer + # EXPR_END: Integer + # EXPR_CLASS: Integer + # EXPR_VALUE: Integer + # EXPR_ARG: Integer + # EXPR_CMDARG: Integer + # EXPR_ENDARG: Integer + # EXPR_ENDFN: Integer # # class Lexer < Ripper # class State diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index 61f36e09bed8ef..4457c26fbe04cf 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -11,8 +11,7 @@ module Prism class Node #: (*String replacements) -> void def deprecated(*replacements) # :nodoc: - location = caller_locations(1, 1) - location = location[0].label if location + location = caller_locations(1, 1)&.[](0)&.label suggest = replacements.map { |replacement| "#{self.class}##{replacement}" } warn(<<~MSG, uplevel: 1, category: :deprecated) diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb index 82e72bd564102e..450c7902266df8 100644 --- a/lib/prism/parse_result/newlines.rb +++ b/lib/prism/parse_result/newlines.rb @@ -114,56 +114,56 @@ def newline_flag!(lines) # :nodoc: end class BeginNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: # Never mark BeginNode with a newline flag, mark children instead. end end class ParenthesesNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: # Never mark ParenthesesNode with a newline flag, mark children instead. end end class IfNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class UnlessNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class UntilNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class WhileNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: predicate.newline_flag!(lines) end end class RescueModifierNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: expression.newline_flag!(lines) end end class InterpolatedMatchLastLineNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -171,7 +171,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedRegularExpressionNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -179,7 +179,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedStringNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -187,7 +187,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedSymbolNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first @@ -195,7 +195,7 @@ def newline_flag!(lines) # :nodoc: end class InterpolatedXStringNode < Node - # @rbs override + #: (Array[bool] lines) -> void def newline_flag!(lines) # :nodoc: first = parts.first first.newline_flag!(lines) if first diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb index cbfb2c69b7267a..be0493df053677 100644 --- a/lib/prism/pattern.rb +++ b/lib/prism/pattern.rb @@ -94,9 +94,9 @@ def compile # matches the pattern. If no block is given, an enumerator will be returned # that will yield each node that matches the pattern. #-- - #: (node root) { (node) -> void } -> void #: (node root) -> Enumerator[node, void] - def scan(root) + #: (node root) { (node) -> void } -> void + def scan(root, &blk) return to_enum(:scan, root) unless block_given? @compiled ||= compile diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index f3cba41a4338bf..ca2db717baffc2 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -110,19 +110,31 @@ Gem::Specification.new do |spec| "lib/prism/translation/ruby_parser.rb", "lib/prism/visitor.rb", "prism.gemspec", - "rbi/prism.rbi", - "rbi/prism/compiler.rbi", - "rbi/prism/dsl.rbi", - "rbi/prism/inspect_visitor.rbi", - "rbi/prism/node_ext.rbi", - "rbi/prism/node.rbi", - "rbi/prism/parse_result.rbi", - "rbi/prism/reflection.rbi", - "rbi/prism/string_query.rbi", + "rbi/generated/prism.rbi", + "rbi/generated/prism/compiler.rbi", + "rbi/generated/prism/desugar_compiler.rbi", + "rbi/generated/prism/dispatcher.rbi", + "rbi/generated/prism/dot_visitor.rbi", + "rbi/generated/prism/dsl.rbi", + "rbi/generated/prism/inspect_visitor.rbi", + "rbi/generated/prism/lex_compat.rbi", + "rbi/generated/prism/mutation_compiler.rbi", + "rbi/generated/prism/node.rbi", + "rbi/generated/prism/node_ext.rbi", + "rbi/generated/prism/parse_result.rbi", + "rbi/generated/prism/pattern.rbi", + "rbi/generated/prism/reflection.rbi", + "rbi/generated/prism/relocation.rbi", + "rbi/generated/prism/serialize.rbi", + "rbi/generated/prism/string_query.rbi", + "rbi/generated/prism/translation.rbi", + "rbi/generated/prism/visitor.rbi", + "rbi/generated/prism/parse_result/comments.rbi", + "rbi/generated/prism/parse_result/errors.rbi", + "rbi/generated/prism/parse_result/newlines.rbi", "rbi/prism/translation/parser.rbi", "rbi/prism/translation/parser_versions.rbi", "rbi/prism/translation/ripper.rbi", - "rbi/prism/visitor.rbi", "sig/generated/prism.rbs", "sig/generated/prism/compiler.rbs", "sig/generated/prism/desugar_compiler.rbs", diff --git a/prism/templates/lib/prism/dot_visitor.rb.erb b/prism/templates/lib/prism/dot_visitor.rb.erb index bdac9cfd3c6c0d..88ef1e1f36b18e 100644 --- a/prism/templates/lib/prism/dot_visitor.rb.erb +++ b/prism/templates/lib/prism/dot_visitor.rb.erb @@ -126,7 +126,7 @@ module Prism end <%- nodes.each do |node| -%> - # (<%= node.name %>) -> void + #: (<%= node.name %>) -> void def visit_<%= node.human %>(node) # :nodoc: table = Table.new("<%= node.name %>") id = node_id(node) diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index 2e42eeea13104d..817b59b477b529 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -294,7 +294,7 @@ module Prism # node.breadth_first_search { |node| node.node_id == node_id } #-- #: () { (node) -> bool } -> node? - def breadth_first_search(&block) + def breadth_first_search(&blk) queue = [self] #: Array[node] while (node = queue.shift) @@ -313,7 +313,7 @@ module Prism # node.breadth_first_search_all { |node| node.is_a?(Prism::CallNode) } #-- #: () { (node) -> bool } -> Array[node] - def breadth_first_search_all(&block) + def breadth_first_search_all(&blk) queue = [self] #: Array[Prism::node] results = [] #: Array[Prism::node] @@ -366,9 +366,9 @@ module Prism # an enumerator that contains each child node. Excludes any `nil`s in # the place of optional nodes that were not present. #-- - #: () { (node) -> void } -> void #: () -> Enumerator[node, void] - def each_child_node + #: () { (node) -> void } -> void + def each_child_node(&blk) raise NoMethodError, "undefined method `each_child_node' for #{inspect}" end @@ -481,9 +481,9 @@ module Prism # See Node.each_child_node. #-- - #: () { (node) -> void } -> void #: () -> Enumerator[node, void] - def each_child_node + #: () { (node) -> void } -> void + def each_child_node(&blk) return to_enum(:each_child_node) unless block_given? <%- node.fields.each do |field| -%> @@ -705,7 +705,7 @@ module Prism <%- end -%> # :section: - #: (untyped other) -> bool + #: (untyped other) -> boolish def ===(other) # :nodoc: other.is_a?(<%= node.name %>)<%= " &&" if (fields = [*node.flags, *node.fields]).any? %> <%- fields.each_with_index do |field, index| -%> diff --git a/prism/templates/template.rb b/prism/templates/template.rb index a39f8bbb784297..e571c58bf298a8 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -166,16 +166,6 @@ def call_seq_type end end - def rbi_class - if specific_kind - "Prism::#{specific_kind}" - elsif union_kind - "T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})" - else - "Prism::Node" - end - end - def check_field_kind if union_kind "[#{union_kind.join(', ')}].include?(#{name}.class)" @@ -208,16 +198,6 @@ def call_seq_type end end - def rbi_class - if specific_kind - "T.nilable(Prism::#{specific_kind})" - elsif union_kind - "T.nilable(T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")}))" - else - "T.nilable(Prism::Node)" - end - end - def check_field_kind if union_kind "[#{union_kind.join(', ')}, NilClass].include?(#{name}.class)" @@ -254,16 +234,6 @@ def call_seq_type end end - def rbi_class - if specific_kind - "T::Array[Prism::#{specific_kind}]" - elsif union_kind - "T::Array[T.any(#{union_kind.map { |kind| "Prism::#{kind}" }.join(", ")})]" - else - "T::Array[Prism::Node]" - end - end - def java_type "#{super}[]" end @@ -288,10 +258,6 @@ def call_seq_type "Symbol" end - def rbi_class - "Symbol" - end - def java_type JAVA_STRING_TYPE end @@ -308,10 +274,6 @@ def call_seq_type "Symbol | nil" end - def rbi_class - "T.nilable(Symbol)" - end - def java_type JAVA_STRING_TYPE end @@ -328,10 +290,6 @@ def call_seq_type "Array[Symbol]" end - def rbi_class - "T::Array[Symbol]" - end - def java_type "#{JAVA_STRING_TYPE}[]" end @@ -347,10 +305,6 @@ def call_seq_type "String" end - def rbi_class - "String" - end - def java_type "byte[]" end @@ -370,10 +324,6 @@ def call_seq_type "Location" end - def rbi_class - "Prism::Location" - end - def java_type "Location" end @@ -393,10 +343,6 @@ def call_seq_type "Location | nil" end - def rbi_class - "T.nilable(Prism::Location)" - end - def java_type "Location" end @@ -412,10 +358,6 @@ def call_seq_type "Integer" end - def rbi_class - "Integer" - end - def java_type "int" end @@ -431,10 +373,6 @@ def call_seq_type "Integer" end - def rbi_class - "Integer" - end - def java_type "int" end @@ -451,10 +389,6 @@ def call_seq_type "Integer" end - def rbi_class - "Integer" - end - def java_type "Object" end @@ -471,10 +405,6 @@ def call_seq_type "Float" end - def rbi_class - "Float" - end - def java_type "double" end @@ -629,8 +559,7 @@ def render(name, write_to: nil) extension = File.extname(filepath.gsub(".erb", "")) heading = - case extension - when ".rb" + if extension == ".rb" <<~HEADING # frozen_string_literal: true # :markup: markdown @@ -643,17 +572,6 @@ def render(name, write_to: nil) ++ =end - HEADING - when ".rbi" - <<~HEADING - # typed: strict - - =begin - This file is generated by the templates/template.rb script and should not be - modified manually. See #{filepath} - if you are looking to modify the template - =end - HEADING else <<~HEADING @@ -738,10 +656,7 @@ def locals "src/node.c", "src/prettyprint.c", "src/serialize.c", - "src/token_type.c", - "rbi/prism/dsl.rbi", - "rbi/prism/node.rbi", - "rbi/prism/visitor.rbi" + "src/token_type.c" ] end end From b52cb82d3c7b2aacdc7b8a452ffe74dbef54db26 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 25 Feb 2026 12:08:36 -0500 Subject: [PATCH 24/35] ZJIT: Format negative offsets as small negative hex values (#16248) Before: Optimized HIR: fn block in
@benchmarks/setivar.rb:40: bb1(): EntryPoint interpreter v1:BasicObject = LoadSelf Jump bb3(v1) bb2(): EntryPoint JIT(0) v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): v10:CPtr = GetEP 1 v11:BasicObject = LoadField v10, :obj@0xffffffffffffffe8 <= PatchPoint NoSingletonClass(TheClass@0x1037633c0) PatchPoint MethodRedefined(TheClass@0x1037633c0, set_value_loop@0xf5f1, cme:0x103a14650) v21:HeapObject[class_exact:TheClass] = GuardType v11, HeapObject[class_exact:TheClass] v22:BasicObject = SendDirect v21, 0x16cf41630, :set_value_loop (0x16cf41670) CheckInterrupts Return v22 After: Optimized HIR: fn block in
@benchmarks/setivar.rb:40: bb1(): EntryPoint interpreter v1:BasicObject = LoadSelf Jump bb3(v1) bb2(): EntryPoint JIT(0) v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): v10:CPtr = GetEP 1 v11:BasicObject = LoadField v10, :obj@-0x18 <= PatchPoint NoSingletonClass(TheClass@0x102bc3420) PatchPoint MethodRedefined(TheClass@0x102bc3420, set_value_loop@0xf5f1, cme:0x102e945f0) v21:HeapObject[class_exact:TheClass] = GuardType v11, HeapObject[class_exact:TheClass] v22:BasicObject = SendDirect v21, 0x16dadd630, :set_value_loop (0x16dadd670) CheckInterrupts Return v22 --- zjit/src/hir.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a1fc0e90cff8b2..e188f4aca89826 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -431,6 +431,16 @@ impl PtrPrintMap { } } +struct Offset(i32); + +impl std::fmt::LowerHex for Offset { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let prefix = if f.alternate() { "0x" } else { "" }; + let bare_hex = format!("{:x}", self.0.abs()); + f.pad_integral(self.0 >= 0, prefix, &bare_hex) + } +} + impl PtrPrintMap { /// Map a pointer for printing pub fn map_ptr(&self, ptr: *const T) -> *const T { @@ -467,8 +477,8 @@ impl PtrPrintMap { self.map_ptr(id as *const c_void) } - fn map_offset(&self, id: i32) -> *const c_void { - self.map_ptr(id as *const c_void) + fn map_offset(&self, id: i32) -> Offset { + Offset(self.map_ptr(id as *const c_void) as i32) } /// Map shape ID into a pointer for printing @@ -1644,8 +1654,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { &Insn::GetEP { level } => write!(f, "GetEP {level}"), Insn::GetLEP => write!(f, "GetLEP"), Insn::LoadSelf => write!(f, "LoadSelf"), - &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:p}", id.contents_lossy(), self.ptr_map.map_offset(offset)), - &Insn::StoreField { recv, id, offset, val } => write!(f, "StoreField {recv}, :{}@{:p}, {val}", id.contents_lossy(), self.ptr_map.map_offset(offset)), + &Insn::LoadField { recv, id, offset, return_type: _ } => write!(f, "LoadField {recv}, :{}@{:#x}", id.contents_lossy(), self.ptr_map.map_offset(offset)), + &Insn::StoreField { recv, id, offset, val } => write!(f, "StoreField {recv}, :{}@{:#x}, {val}", id.contents_lossy(), self.ptr_map.map_offset(offset)), &Insn::WriteBarrier { recv, val } => write!(f, "WriteBarrier {recv}, {val}"), Insn::SetIvar { self_val, id, val, .. } => write!(f, "SetIvar {self_val}, :{}, {val}", id.contents_lossy()), Insn::GetGlobal { id, .. } => write!(f, "GetGlobal :{}", id.contents_lossy()), From 2168fdbdd4f869008aaafea4b7a83a012eee4184 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Wed, 25 Feb 2026 16:23:02 +0000 Subject: [PATCH 25/35] Move class pre-aging out of the allocation path Previously classes and modules were pre-aged. Ie. as soon as they're allocated they are aged to old_age - 1. This was done with the assumption that classes are generally always long lived, so we should assume that any that survive a single GC can be considered old. This commit keeps the same semantics, but moves the logic out of the allocation path, in order to simplify allocation. Classes and modules are now set to old-age the first time they are marked. --- gc/default/default.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/gc/default/default.c b/gc/default/default.c index 046aa146f73055..1099d6e0dc11e5 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2175,10 +2175,6 @@ newobj_init(VALUE klass, VALUE flags, int wb_protected, rb_objspace_t *objspace, RBASIC(obj)->shape_id = 0; #endif - int t = flags & RUBY_T_MASK; - if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) { - RVALUE_AGE_SET_CANDIDATE(objspace, obj); - } #if RACTOR_CHECK_MODE void rb_ractor_setup_belonging(VALUE obj); @@ -4407,8 +4403,16 @@ gc_aging(rb_objspace_t *objspace, VALUE obj) if (!RVALUE_PAGE_WB_UNPROTECTED(page, obj)) { if (!RVALUE_OLD_P(objspace, obj)) { - gc_report(3, objspace, "gc_aging: YOUNG: %s\n", rb_obj_info(obj)); - RVALUE_AGE_INC(objspace, obj); + int t = BUILTIN_TYPE(obj); + if (t == T_CLASS || t == T_MODULE || t == T_ICLASS) { + gc_report(3, objspace, "gc_aging: YOUNG class: %s\n", rb_obj_info(obj)); + RVALUE_AGE_SET(obj, RVALUE_OLD_AGE); + RVALUE_OLD_UNCOLLECTIBLE_SET(objspace, obj); + } + else { + gc_report(3, objspace, "gc_aging: YOUNG: %s\n", rb_obj_info(obj)); + RVALUE_AGE_INC(objspace, obj); + } } else if (is_full_marking(objspace)) { GC_ASSERT(RVALUE_PAGE_UNCOLLECTIBLE(page, obj) == FALSE); From ad380f8752c93e4c763329825ebf271e583b2b3c Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Wed, 25 Feb 2026 10:58:02 +0000 Subject: [PATCH 26/35] Correct timezone type: It's Asia/Tokyo, not Tokyo/Asia https://timezonedb.com/time-zones/Asia/Tokyo --- .github/workflows/check_misc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 1be94a016e4126..e900f0912372a2 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -70,7 +70,7 @@ jobs: run: | date +"mon=%-m"%n"day=%-d" >> $GITHUB_OUTPUT env: - TZ: Tokyo/Asia + TZ: Asia/Tokyo - id: deprecation run: | From b3a1286d5c921da8ee9a1cee05b3d4c3d588efee Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 24 Feb 2026 11:58:47 -0800 Subject: [PATCH 27/35] Avoid duplicate struct name in rb_raw_obj_info Previously we printed the struct name of T_DATA both in rb_raw_obj_info_buitin_type and in rb_raw_obj_info_common via type_name. --- gc.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gc.c b/gc.c index 2a4dec32e9119f..1af2ba41e7bf73 100644 --- a/gc.c +++ b/gc.c @@ -5054,12 +5054,6 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU APPEND_F("r:%d", r->pub.id); } } - else { - const char * const type_name = rb_objspace_data_type_name(obj); - if (type_name) { - APPEND_F("%s", type_name); - } - } break; } case T_IMEMO: { From 9ab1dfadc9ec2b435875b4d2ec98479f7defb690 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 25 Feb 2026 13:44:16 -0800 Subject: [PATCH 28/35] ZJIT: Decouple gen_function_stub and gen_function_stub_hit_trampoline (#16249) Before this change, gen_function_stub and gen_function_stub_hit_trampoline communicated via a scratch register. We would like gen_function_stub_hit_trampoline to have more freedom with regard to the registers it uses, especially for the CCall in to function_stub_hit. Instead of communicating via scratch register, we'll communicate via stack. Practically speaking, this means: * Stop using x15 (scratch reg) to communicate iseq call addr from call stub to function sub hit trampoline; use stack instead * Don't try to CCall with x15 as first argument; can't use scratch reg in parallel move of arguments Here is pseudo assembly of before this commit: ``` some_send_direct_in_a_ruby_method(JIT code): mov x15, gen_function_stub mov x0, self mov x1, 1 blr x15 gen_function_stub: mov x15, 0xISEQADDR (the address of the ISEQ we _want_ to compile) jmp function_stub_hit_trampoline function_stub_hit_trampoline: function prologue cpush ALL_JIT_REGS mov x0, x15 # currently x15 is 0xISEQADDR mov x1, CFP mov x2, SP blr function_stub_hit mov x15, x0 # write jump address to x15 (code pointer for compiled iseq) cpop ALL_JIT_REGS function epilogue jmp x15 ``` Here is pseudo assembly of after this commit: ``` some_send_direct_in_a_ruby_method(JIT code): mov x15, gen_function_stub mov x0, self mov x1, 1 blr x15 gen_function_stub: mov x15, 0xISEQADDR (the address of the ISEQ we _want_ to compile) push x15 jmp function_stub_hit_trampoline function_stub_hit_trampoline: pop x15 # get the ISEQ addr from gen_function_stub function prologue cpush ALL_JIT_REGS mov x0, x15 # currently x15 is 0xISEQADDR mov x1, CFP mov x2, SP blr function_stub_hit mov x15, x0 # write jump address to x15 (code pointer for compiled iseq) cpop ALL_JIT_REGS function epilogue jmp x15 ``` --- zjit/src/codegen.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 6a705b90dbdaff..c1c5b72690adc5 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2815,6 +2815,7 @@ fn gen_function_stub(cb: &mut CodeBlock, iseq_call: IseqCallRef) -> Result Result Result Date: Thu, 26 Feb 2026 20:32:00 +0900 Subject: [PATCH 29/35] git must in in the source tree instead of the build directory --- defs/gmake.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index ea12729f148e92..087aa1b549676b 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -276,7 +276,7 @@ pull-github: fetch-github $(call pull-github,$(PR)) define pull-github - $(eval GITHUB_MERGE_BASE := $(shell $(GIT) rev-parse HEAD) + $(eval GITHUB_MERGE_BASE := $(shell $(GIT_IN_SRC) rev-parse HEAD) $(eval GITHUB_MERGE_BRANCH := $(shell $(GIT_IN_SRC) symbolic-ref --short HEAD)) $(eval GITHUB_MERGE_WORKTREE := $(shell mktemp -d "$(srcdir)/gh-$(1)-XXXXXX")) $(GIT_IN_SRC) worktree prune @@ -434,7 +434,7 @@ ifneq ($(DOT_WAIT),) endif ifeq ($(HAVE_GIT),yes) -REVISION_LATEST := $(shell $(GIT) rev-parse HEAD) +REVISION_LATEST := $(shell $(GIT_IN_SRC) rev-parse HEAD) else REVISION_LATEST := update endif From 3a7f3cf37139fc54305a0d413fee45d68519a5a0 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Wed, 25 Feb 2026 20:15:43 +0100 Subject: [PATCH 30/35] [ruby/openssl] Fix missing error check on BIO_get_mem_ptr() LibreSSL docs state this can fail. OpenSSL has no docs for this, but the implementation goes via BIO_ctrl() which can fail (e.g. via a callback). Example Valgrind trace: ``` Invalid read of size 1 memmove (at /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) memcpy (string_fortified.h:29) ossl_str_new (ossl.c:107) ossl_membio2str (ossl_bio.c:36) ossl_x509ext_get_value (ossl_x509ext.c:390) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_catch_obj (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_catch_obj (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_invoke_proc (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_proc_call_kw (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) ruby_run_node (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) (below main) (libc_start_call_main.h:58) ``` https://github.com/ruby/openssl/commit/4fb09446d6 --- ext/openssl/ossl_bio.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl_bio.c b/ext/openssl/ossl_bio.c index 4edde5091d0933..cc03c5d5f720eb 100644 --- a/ext/openssl/ossl_bio.c +++ b/ext/openssl/ossl_bio.c @@ -32,7 +32,11 @@ ossl_membio2str(BIO *bio) int state; BUF_MEM *buf; - BIO_get_mem_ptr(bio, &buf); + if (BIO_get_mem_ptr(bio, &buf) <= 0) { + BIO_free(bio); + ossl_raise(eOSSLError, "BIO_get_mem_ptr"); + } + ret = ossl_str_new(buf->data, buf->length, &state); BIO_free(bio); if (state) From 64fdfb722bcc36ffb6a41b024707a77b8cedc84e Mon Sep 17 00:00:00 2001 From: ndossche Date: Tue, 24 Feb 2026 15:43:25 +0100 Subject: [PATCH 31/35] [ruby/openssl] Fix memory leak if sk_push fails If it fails, then the duplicated item is not freed. Solve it by making sure the push cannot fail by reserving the necessary stack size upfront. Example Valgrind trace: ``` 14,009 (384 direct, 13,625 indirect) bytes in 1 blocks are definitely lost in loss record 27,287 of 27,373 malloc (at /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) CRYPTO_zalloc (at /usr/lib/x86_64-linux-gnu/libcrypto.so.3) ASN1_item_new (at /usr/lib/x86_64-linux-gnu/libcrypto.so.3) *ossl_x509_alloc (ossl_x509cert.c:97) rb_class_new_instance_pass_kw (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_catch_obj (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_yield (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_ary_each (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_catch_obj (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) rb_vm_exec (at /usr/lib/x86_64-linux-gnu/libruby-3.2.so.3.2.3) ``` https://github.com/ruby/openssl/commit/b53628b127 --- ext/openssl/ossl.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 98127fcba02314..d3270f31f5b916 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -34,7 +34,11 @@ ossl_##name##_ary2sk0(VALUE ary) \ " of class ##type##"); \ } \ x = dup(val); /* NEED TO DUP */ \ - sk_##type##_push(sk, x); \ + if (!sk_##type##_push(sk, x)) { \ + type##_free(x); \ + sk_##type##_pop_free(sk, type##_free); \ + ossl_raise(eOSSLError, NULL); \ + } \ } \ return (VALUE)sk; \ } \ From 8eab3172788ac1f2760d7df46e07e5b0ae7e7ea6 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 24 Feb 2026 16:30:43 -0800 Subject: [PATCH 32/35] Convert cc_refinement_table into TypedData object This converts the cc_refinement_table in the VM into a standard TypedData object using rb_gc_declare_weak_references to allow it to be . This allows us to store all the refinements in a flat array, and simply remove them as that are GC'd, we don't need a set for efficient deletes. This should also allow us to assume that imemo_callcache never requires additional cleanup. --- gc.c | 37 -------------- gc/gc.h | 1 - method.h | 1 - vm.c | 11 ++--- vm_core.h | 2 +- vm_method.c | 138 ++++++++++++++++++++++++++++++++++++---------------- 6 files changed, 102 insertions(+), 88 deletions(-) diff --git a/gc.c b/gc.c index 1af2ba41e7bf73..bc5a523f711b5c 100644 --- a/gc.c +++ b/gc.c @@ -2270,15 +2270,6 @@ rb_gc_obj_free_vm_weak_references(VALUE obj) break; case T_IMEMO: switch (imemo_type(obj)) { - case imemo_callcache: { - const struct rb_callcache *cc = (const struct rb_callcache *)obj; - - if (vm_cc_refinement_p(cc)) { - rb_vm_delete_cc_refinement(cc); - } - - break; - } case imemo_callinfo: rb_vm_ci_free((const struct rb_callinfo *)obj); break; @@ -4062,23 +4053,6 @@ vm_weak_table_foreach_update_weak_key(st_data_t *key, st_data_t *value, st_data_ return ret; } -static int -vm_weak_table_cc_refinement_foreach(st_data_t key, st_data_t data, int error) -{ - struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; - - return iter_data->callback((VALUE)key, iter_data->data); -} - -static int -vm_weak_table_cc_refinement_foreach_update_update(st_data_t *key, st_data_t data, int existing) -{ - struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data; - - return iter_data->update_callback((VALUE *)key, iter_data->data); -} - - static int vm_weak_table_sym_set_foreach(VALUE *sym_ptr, void *data) { @@ -4275,17 +4249,6 @@ rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback, ); break; } - case RB_GC_VM_CC_REFINEMENT_TABLE: { - if (vm->cc_refinement_table) { - set_foreach_with_replace( - vm->cc_refinement_table, - vm_weak_table_cc_refinement_foreach, - vm_weak_table_cc_refinement_foreach_update_update, - (st_data_t)&foreach_data - ); - } - break; - } case RB_GC_VM_WEAK_TABLE_COUNT: rb_bug("Unreachable"); default: diff --git a/gc/gc.h b/gc/gc.h index 5979b4a00193e2..469a4902f03365 100644 --- a/gc/gc.h +++ b/gc/gc.h @@ -31,7 +31,6 @@ enum rb_gc_vm_weak_tables { RB_GC_VM_ID2REF_TABLE, RB_GC_VM_GENERIC_FIELDS_TABLE, RB_GC_VM_FROZEN_STRINGS_TABLE, - RB_GC_VM_CC_REFINEMENT_TABLE, RB_GC_VM_WEAK_TABLE_COUNT }; diff --git a/method.h b/method.h index 260344d53b58ba..fdbab41cf71bba 100644 --- a/method.h +++ b/method.h @@ -264,7 +264,6 @@ void rb_scope_visibility_set(rb_method_visibility_t); VALUE rb_unnamed_parameters(int arity); void rb_vm_insert_cc_refinement(const struct rb_callcache *cc); -void rb_vm_delete_cc_refinement(const struct rb_callcache *cc); void rb_clear_method_cache(VALUE klass_or_module, ID mid); void rb_clear_all_refinement_method_cache(void); diff --git a/vm.c b/vm.c index a078f9e7344e4c..4f9c1c7402dbf4 100644 --- a/vm.c +++ b/vm.c @@ -3236,6 +3236,7 @@ rb_vm_update_references(void *ptr) vm->self = rb_gc_location(vm->self); vm->mark_object_ary = rb_gc_location(vm->mark_object_ary); vm->orig_progname = rb_gc_location(vm->orig_progname); + vm->cc_refinement_set = rb_gc_location(vm->cc_refinement_set); if (vm->root_box) rb_box_gc_update_references(vm->root_box); @@ -3324,6 +3325,7 @@ rb_vm_mark(void *ptr) rb_gc_mark_movable(vm->orig_progname); rb_gc_mark_movable(vm->coverages); rb_gc_mark_movable(vm->me2counter); + rb_gc_mark_movable(vm->cc_refinement_set); rb_gc_mark_values(RUBY_NSIG, vm->trap_list.cmd); @@ -3414,10 +3416,6 @@ ruby_vm_destruct(rb_vm_t *vm) st_free_table(vm->ci_table); vm->ci_table = NULL; } - if (vm->cc_refinement_table) { - rb_set_free_table(vm->cc_refinement_table); - vm->cc_refinement_table = NULL; - } RB_ALTSTACK_FREE(vm->main_altstack); struct global_object_list *next; @@ -3510,7 +3508,6 @@ vm_memsize(const void *ptr) vm_memsize_builtin_function_table(vm->builtin_function_table) + rb_id_table_memsize(vm->negative_cme_table) + rb_st_memsize(vm->overloaded_cme_table) + - rb_set_memsize(vm->cc_refinement_table) + vm_memsize_constant_cache() ); @@ -4736,6 +4733,8 @@ rb_vm_register_global_object(VALUE obj) } } +VALUE rb_cc_refinement_set_create(void); + void Init_vm_objects(void) { @@ -4744,7 +4743,7 @@ Init_vm_objects(void) /* initialize mark object array, hash */ vm->mark_object_ary = pin_array_list_new(Qnil); vm->ci_table = st_init_table(&vm_ci_hashtype); - vm->cc_refinement_table = rb_set_init_numtable(); + vm->cc_refinement_set = rb_cc_refinement_set_create(); } // Whether JIT is enabled or not, we need to load/undef `#with_jit` for other builtins. diff --git a/vm_core.h b/vm_core.h index 7fcf8ca5c14439..ebc3fc5d27b677 100644 --- a/vm_core.h +++ b/vm_core.h @@ -816,7 +816,7 @@ typedef struct rb_vm_struct { struct rb_id_table *negative_cme_table; st_table *overloaded_cme_table; // cme -> overloaded_cme set_table *unused_block_warning_table; - set_table *cc_refinement_table; + VALUE cc_refinement_set; // This id table contains a mapping from ID to ICs. It does this with ID // keys and nested st_tables as values. The nested tables have ICs as keys diff --git a/vm_method.c b/vm_method.c index 5289bf03f8e741..dc314950df40d7 100644 --- a/vm_method.c +++ b/vm_method.c @@ -587,32 +587,6 @@ rb_invalidate_method_caches(struct rb_id_table *cm_tbl, VALUE cc_tbl) } } -static int -invalidate_cc_refinement(st_data_t key, st_data_t data) -{ - VALUE v = (VALUE)key; - void *ptr = rb_asan_poisoned_object_p(v); - rb_asan_unpoison_object(v, false); - - if (rb_gc_pointer_to_heap_p(v) && - !rb_objspace_garbage_object_p(v) && - RBASIC(v)->flags) { // liveness check - const struct rb_callcache *cc = (const struct rb_callcache *)v; - - VM_ASSERT(vm_cc_refinement_p(cc)); - - if (vm_cc_valid(cc)) { - vm_cc_invalidate(cc); - } - } - - if (ptr) { - rb_asan_poison_object(v); - } - - return ST_CONTINUE; -} - static st_index_t vm_ci_hash(VALUE v) { @@ -722,28 +696,94 @@ rb_vm_ci_free(const struct rb_callinfo *ci) st_delete(vm->ci_table, &key, NULL); } -void -rb_vm_insert_cc_refinement(const struct rb_callcache *cc) +struct cc_refinement_entries { + VALUE *entries; + size_t len; + size_t capa; +}; + +static void +cc_refinement_set_free(void *ptr) { - st_data_t key = (st_data_t)cc; + struct cc_refinement_entries *e = ptr; + xfree(e->entries); +} - rb_vm_t *vm = GET_VM(); - RB_VM_LOCK_ENTER(); - { - rb_set_insert(vm->cc_refinement_table, key); +static size_t +cc_refinement_set_memsize(const void *ptr) +{ + const struct cc_refinement_entries *e = ptr; + return e->capa * sizeof(VALUE); +} + +static void +cc_refinement_set_compact(void *ptr) +{ + struct cc_refinement_entries *e = ptr; + for (size_t i = 0; i < e->len; i++) { + e->entries[i] = rb_gc_location(e->entries[i]); } - RB_VM_LOCK_LEAVE(); } -void -rb_vm_delete_cc_refinement(const struct rb_callcache *cc) +static void +cc_refinement_set_handle_weak_references(void *ptr) { - ASSERT_vm_locking(); + struct cc_refinement_entries *e = ptr; + size_t write = 0; + for (size_t read = 0; read < e->len; read++) { + if (rb_gc_handle_weak_references_alive_p(e->entries[read])) { + e->entries[write++] = e->entries[read]; + } + } + e->len = write; +} + +static const rb_data_type_t cc_refinement_set_type = { + "VM/cc_refinement_set", + { + NULL, + cc_refinement_set_free, + cc_refinement_set_memsize, + cc_refinement_set_compact, + cc_refinement_set_handle_weak_references, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE +}; + +VALUE +rb_cc_refinement_set_create(void) +{ + struct cc_refinement_entries *e; + VALUE obj = TypedData_Make_Struct(0, struct cc_refinement_entries, &cc_refinement_set_type, e); + + e->entries = NULL; + e->len = 0; + e->capa = 0; + + rb_gc_declare_weak_references(obj); + + return obj; +} +void +rb_vm_insert_cc_refinement(const struct rb_callcache *cc) +{ rb_vm_t *vm = GET_VM(); - st_data_t key = (st_data_t)cc; + RB_VM_LOCK_ENTER(); + { + struct cc_refinement_entries *e = RTYPEDDATA_GET_DATA(vm->cc_refinement_set); + if (e->len == e->capa) { + size_t new_capa = e->capa == 0 ? 16 : e->capa * 2; + SIZED_REALLOC_N(e->entries, VALUE, new_capa, e->capa); + e->capa = new_capa; + } + e->entries[e->len++] = (VALUE)cc; - rb_set_table_delete(vm->cc_refinement_table, &key); + // We never mark the cc, but we need to issue a writebarrier so that + // the refinement set can be added to the remembered set + RB_OBJ_WRITTEN(vm->cc_refinement_set, Qundef, (VALUE)cc); + } + RB_VM_LOCK_LEAVE(); } void @@ -753,9 +793,23 @@ rb_clear_all_refinement_method_cache(void) RB_VM_LOCK_ENTER(); { - rb_set_table_foreach(vm->cc_refinement_table, invalidate_cc_refinement, (st_data_t)NULL); - rb_set_table_clear(vm->cc_refinement_table); - rb_set_compact_table(vm->cc_refinement_table); + struct cc_refinement_entries *e = RTYPEDDATA_GET_DATA(vm->cc_refinement_set); + for (size_t i = 0; i < e->len; i++) { + VALUE v = e->entries[i]; + + // All objects should be live as weak references are pruned in + // cc_refinement_set_handle_weak_references + VM_ASSERT(rb_gc_pointer_to_heap_p(v)); + VM_ASSERT(!rb_objspace_garbage_object_p(v)); + + const struct rb_callcache *cc = (const struct rb_callcache *)v; + VM_ASSERT(vm_cc_refinement_p(cc)); + + if (vm_cc_valid(cc)) { + vm_cc_invalidate(cc); + } + } + e->len = 0; } RB_VM_LOCK_LEAVE(); From 4455fdc201981122c67e9913d972b3cbd7b93e47 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Tue, 24 Feb 2026 20:38:36 -0800 Subject: [PATCH 33/35] Mark callcache as !needs_cleanup_p --- gc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gc.c b/gc.c index bc5a523f711b5c..26e65a90f3bc74 100644 --- a/gc.c +++ b/gc.c @@ -1278,6 +1278,7 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) case imemo_ifunc: case imemo_memo: case imemo_svar: + case imemo_callcache: case imemo_throw_data: return false; default: From b4aa7462be9913d1d3739bf56a0a71a763a5d4a3 Mon Sep 17 00:00:00 2001 From: ReLU <82130499+ykawabata17@users.noreply.github.com> Date: Fri, 27 Feb 2026 01:43:37 +0900 Subject: [PATCH 34/35] ZJIT: Fix ArgumentError for hash as positional arg to keyword-only method (GH-16223) --- zjit/src/codegen_tests.rs | 32 ++++++++++++++++++++++++++++++++ zjit/src/hir.rs | 28 +++++++++++++++++----------- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index ae5841ea7ddda5..980441c49104d9 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -821,6 +821,38 @@ fn test_send_kwarg_optional_static_with_side_exit() { "), @"[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(" diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e188f4aca89826..ba6ce8365eb4a1 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1913,31 +1913,37 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq return false; } - // Because we exclude e.g. post parameters above, they are also excluded from the sum below. + // Because we exclude e.g. post parameters above, they are also excluded from the checks below. let lead_num = params.lead_num; let opt_num = params.opt_num; let keyword = params.keyword; let kw_req_num = if keyword.is_null() { 0 } else { unsafe { (*keyword).required_num } }; let kw_total_num = if keyword.is_null() { 0 } else { unsafe { (*keyword).num } }; - // Minimum args: all required positional + all required keywords - let min_argc = lead_num + kw_req_num; - // Maximum args: all positional (required + optional) + all keywords (required + optional) - let max_argc = lead_num + opt_num + kw_total_num; + let kwarg = unsafe { rb_vm_ci_kwarg(ci) }; + let caller_kw_count = if kwarg.is_null() { 0 } else { (unsafe { get_cikw_keyword_len(kwarg) }) as usize }; + let caller_positional = match args.len().checked_sub(caller_kw_count) { + Some(count) => count, + None => { + function.set_dynamic_send_reason(send_insn, ArgcParamMismatch); + return false; + } + }; - can_send = c_int::try_from(args.len()) + let positional_ok = c_int::try_from(caller_positional) .as_ref() - .map(|argc| (min_argc..=max_argc).contains(argc)) + .map(|argc| (lead_num..=lead_num + opt_num).contains(argc)) .unwrap_or(false); - if !can_send { + let keyword_ok = c_int::try_from(caller_kw_count) + .as_ref() + .map(|argc| (kw_req_num..=kw_total_num).contains(argc)) + .unwrap_or(false); + if !positional_ok || !keyword_ok { function.set_dynamic_send_reason(send_insn, ArgcParamMismatch); return false } // asm.ccall() doesn't support 6+ args. Compute the final argc after keyword setup: // final_argc = caller's positional args + callee's total keywords (all kw slots are filled). - let kwarg = unsafe { rb_vm_ci_kwarg(ci) }; - let caller_kw_count = if kwarg.is_null() { 0 } else { (unsafe { get_cikw_keyword_len(kwarg) }) as usize }; - let caller_positional = args.len() - caller_kw_count; // Right now, the JIT entrypoint accepts the block as an param // We may remove it, remove the block_arg addition to match // See: https://github.com/ruby/ruby/pull/15911#discussion_r2710544982 From 01b413c1cd32de475fb9f7173d3dd1e8cb1105df Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 26 Feb 2026 15:47:12 -0500 Subject: [PATCH 35/35] ZJIT: Refine singleton class invariant to only invalidate on method shadowing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the NoSingletonClass invariant invalidated JIT code whenever a singleton class was created for an instance of a given class. This was overly conservative—many uses of singleton classes (freeze, introspection, non-shadowing methods) don't affect method dispatch for the original class. Refine the invariant to NoSingletonClassWithShadowingMethod: only invalidate when a method is defined on a singleton class that shadows a method on the original class. The hook moves from make_singleton_class() in class.c to rb_method_entry_make() in vm_method.c, where it checks whether the newly defined method exists on the superclass chain. Correctness is always maintained by GuardType, which checks the exact class of the receiver—singleton class objects fail this check and side-exit regardless of the patchpoint. Co-Authored-By: Claude Opus 4.6 --- class.c | 1 - vm_method.c | 9 + zjit.h | 4 +- zjit/src/codegen.rs | 6 +- zjit/src/codegen_tests.rs | 21 ++ zjit/src/cruby_methods.rs | 2 +- zjit/src/hir.rs | 78 ++--- zjit/src/hir/opt_tests.rs | 600 ++++++++++++++++++++++++-------------- zjit/src/hir/tests.rs | 4 +- zjit/src/invariants.rs | 47 +-- zjit/src/stats.rs | 10 +- 11 files changed, 488 insertions(+), 294 deletions(-) diff --git a/class.c b/class.c index cc4c6845b49977..d126e5a3f6f80b 100644 --- a/class.c +++ b/class.c @@ -1422,7 +1422,6 @@ make_singleton_class(VALUE obj) RBASIC_SET_CLASS(obj, klass); rb_singleton_class_attached(klass, obj); rb_yjit_invalidate_no_singleton_class(orig_class); - rb_zjit_invalidate_no_singleton_class(orig_class); SET_METACLASS_OF(klass, METACLASS_OF(rb_class_real(orig_class))); return klass; diff --git a/vm_method.c b/vm_method.c index dc314950df40d7..de7fdb212b4f20 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1500,6 +1500,15 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil check_override_opt_method(klass, (VALUE)mid); } + // If a method was added to a singleton class that shadows a method on + // the original class, invalidate JIT code that assumes no shadowing. + if (RCLASS_SINGLETON_P(orig_klass)) { + VALUE super_klass = RCLASS_SUPER(orig_klass); + if (rb_method_entry(super_klass, mid)) { + rb_zjit_invalidate_singleton_class_has_shadowing_method(super_klass); + } + } + return me; } diff --git a/zjit.h b/zjit.h index 47240846ff1db0..82da3c133d7692 100644 --- a/zjit.h +++ b/zjit.h @@ -27,7 +27,7 @@ void rb_zjit_iseq_update_references(void *payload); void rb_zjit_iseq_free(const rb_iseq_t *iseq); void rb_zjit_before_ractor_spawn(void); void rb_zjit_tracing_invalidate_all(void); -void rb_zjit_invalidate_no_singleton_class(VALUE klass); +void rb_zjit_invalidate_singleton_class_has_shadowing_method(VALUE klass); #else #define rb_zjit_entry 0 static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, bool jit_exception) {} @@ -39,7 +39,7 @@ static inline void rb_zjit_invalidate_no_ep_escape(const rb_iseq_t *iseq) {} static inline void rb_zjit_constant_state_changed(ID id) {} static inline void rb_zjit_before_ractor_spawn(void) {} static inline void rb_zjit_tracing_invalidate_all(void) {} -static inline void rb_zjit_invalidate_no_singleton_class(VALUE klass) {} +static inline void rb_zjit_invalidate_singleton_class_has_shadowing_method(VALUE klass) {} #endif // #if USE_ZJIT #define rb_zjit_enabled_p (rb_zjit_entry != 0) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index c1c5b72690adc5..fa6ec96578db95 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -10,7 +10,7 @@ use std::slice; use crate::backend::current::ALLOC_REGS; use crate::invariants::{ track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, - track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_assumption + track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_shadowing_assumption }; use crate::gc::append_gc_offsets; use crate::payload::{get_or_create_iseq_payload, IseqCodePtrs, IseqVersion, IseqVersionRef, IseqStatus}; @@ -904,8 +904,8 @@ pub fn split_patch_point(asm: &mut Assembler, target: &Target, invariant: Invari Invariant::SingleRactorMode => { track_single_ractor_assumption(code_ptr, side_exit_ptr, version); } - Invariant::NoSingletonClass { klass } => { - track_no_singleton_class_assumption(klass, code_ptr, side_exit_ptr, version); + Invariant::NoSingletonClassWithShadowingMethod { klass } => { + track_no_singleton_class_shadowing_assumption(klass, code_ptr, side_exit_ptr, version); } } }); diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 980441c49104d9..9bf6c43ac5ba5f 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -4542,6 +4542,27 @@ fn test_singleton_class_invalidation_optimized_variadic_ccall() { "), @"[1, 1000]"); } +#[test] +fn test_singleton_class_shadowing_method_invalidation() { + // Profile String#length so it gets JIT-compiled, then define a singleton + // method that shadows it, then call and verify the singleton method is used. + assert_snapshot!(inspect(" + def test(s) + s.length + end + + results = [] + results << test('asdf') + + special = 'world' + def special.length = 99 + + results << test(special) + results << test('hello') + results + "), @"[4, 99, 5]"); +} + #[test] fn test_is_a_string_special_case() { assert_snapshot!(inspect(r#" diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index d6cbda0c698144..c443dec59abef0 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -891,7 +891,7 @@ fn inline_kernel_respond_to_p( (_, _) => return None, // not public and include_all not known, can't compile }; // Check singleton class assumption first, before emitting other patchpoints - if !fun.assume_no_singleton_classes(block, recv_class, state) { + if !fun.assume_no_singleton_class_method_shadowing(block, recv_class, state) { return None; } fun.push_insn(block, hir::Insn::PatchPoint { invariant: hir::Invariant::NoTracePoint, state }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ba6ce8365eb4a1..e6ebdd41fd36f1 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -7,7 +7,7 @@ #![allow(clippy::match_like_matches_macro)] use crate::{ backend::lir::C_ARG_OPNDS, - cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, invariants::has_singleton_class_of, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json + cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, invariants::has_singleton_class_method_shadowing, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState, json::Json }; use std::{ cell::RefCell, collections::{BTreeSet, HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, c_int, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter @@ -146,9 +146,9 @@ pub enum Invariant { NoEPEscape(IseqPtr), /// There is one ractor running. If a non-root ractor gets spawned, this is invalidated. SingleRactorMode, - /// Objects of this class have no singleton class. - /// When a singleton class is created for an object of this class, this is invalidated. - NoSingletonClass { + /// No singleton class of an instance of this class has a method that shadows a method + /// on this class. When such a shadowing method is defined, this is invalidated. + NoSingletonClassWithShadowingMethod { klass: VALUE, }, } @@ -284,9 +284,9 @@ impl<'a> std::fmt::Display for InvariantPrinter<'a> { Invariant::NoTracePoint => write!(f, "NoTracePoint"), Invariant::NoEPEscape(iseq) => write!(f, "NoEPEscape({})", &iseq_name(iseq)), Invariant::SingleRactorMode => write!(f, "SingleRactorMode"), - Invariant::NoSingletonClass { klass } => { + Invariant::NoSingletonClassWithShadowingMethod { klass } => { let class_name = get_class_name(klass); - write!(f, "NoSingletonClass({}@{:p})", + write!(f, "NoSingletonClassWithShadowingMethod({}@{:p})", class_name, self.ptr_map.map_ptr(klass.as_ptr::())) } @@ -660,9 +660,9 @@ pub enum SendFallbackReason { ComplexArgPass, /// Caller has keyword arguments but callee doesn't expect them; need to convert to hash. UnexpectedKeywordArgs, - /// A singleton class has been seen for the receiver class, so we skip the optimization - /// to avoid an invalidation loop. - SingletonClassSeen, + /// A singleton class with a shadowing method has been seen for the receiver class, + /// so we skip the optimization to avoid an invalidation loop. + SingletonClassWithShadowingMethodSeen, /// The super call is passed a block that the optimizer does not support. SuperCallWithBlock, /// When the `super` is in a block, finding the running CME for guarding requires a loop. Not @@ -719,7 +719,7 @@ impl Display for SendFallbackReason { ArgcParamMismatch => write!(f, "Argument count does not match parameter count"), ComplexArgPass => write!(f, "Complex argument passing"), UnexpectedKeywordArgs => write!(f, "Unexpected Keyword Args"), - SingletonClassSeen => write!(f, "Singleton class previously created for receiver class"), + SingletonClassWithShadowingMethodSeen => write!(f, "Singleton class with shadowing method previously seen for receiver class"), SuperFromBlock => write!(f, "super: call from within a block"), SuperCallWithBlock => write!(f, "super: call made with a block"), SuperClassNotFound => write!(f, "super: profiled class cannot be found"), @@ -2169,21 +2169,21 @@ impl Function { } } - /// Assume that objects of a given class will have no singleton class. - /// Returns true if safe to assume so and emits a PatchPoint. - /// Returns false if we've already seen a singleton class for this class, - /// to avoid an invalidation loop. - pub fn assume_no_singleton_classes(&mut self, block: BlockId, klass: VALUE, state: InsnId) -> bool { + /// Assume that no singleton class of an instance of a given class will have a method + /// that shadows a method on the class. Returns true if safe to assume so and emits a + /// PatchPoint. Returns false if we've already seen such shadowing, to avoid an + /// invalidation loop. + pub fn assume_no_singleton_class_method_shadowing(&mut self, block: BlockId, klass: VALUE, state: InsnId) -> bool { if !klass.instance_can_have_singleton_class() { // This class can never have a singleton class, so no patchpoint needed. return true; } - if has_singleton_class_of(klass) { - // We've seen a singleton class for this klass. Disable the optimization - // to avoid an invalidation loop. + if has_singleton_class_method_shadowing(klass) { + // We've seen a singleton class with a shadowing method for this klass. + // Disable the optimization to avoid an invalidation loop. return false; } - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass }, state }); + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClassWithShadowingMethod { klass }, state }); true } @@ -2980,7 +2980,7 @@ impl Function { return false; } self.gen_patch_points_for_optimized_ccall(block, class, method_id, cme, state); - if !self.assume_no_singleton_classes(block, class, state) { + if !self.assume_no_singleton_class_method_shadowing(block, class, state) { return false; } true @@ -3199,8 +3199,8 @@ impl Function { } // Check singleton class assumption first, before emitting other patchpoints - if !self.assume_no_singleton_classes(block, klass, state) { - self.set_dynamic_send_reason(insn_id, SingletonClassSeen); + if !self.assume_no_singleton_class_method_shadowing(block, klass, state) { + self.set_dynamic_send_reason(insn_id, SingletonClassWithShadowingMethodSeen); self.push_insn_id(block, insn_id); continue; } @@ -3243,8 +3243,8 @@ impl Function { self.push_insn_id(block, insn_id); continue; } // Check singleton class assumption first, before emitting other patchpoints - if !self.assume_no_singleton_classes(block, klass, state) { - self.set_dynamic_send_reason(insn_id, SingletonClassSeen); + if !self.assume_no_singleton_class_method_shadowing(block, klass, state) { + self.set_dynamic_send_reason(insn_id, SingletonClassWithShadowingMethodSeen); self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); @@ -3267,8 +3267,8 @@ impl Function { self.push_insn_id(block, insn_id); continue; } // Check singleton class assumption first, before emitting other patchpoints - if !self.assume_no_singleton_classes(block, klass, state) { - self.set_dynamic_send_reason(insn_id, SingletonClassSeen); + if !self.assume_no_singleton_class_method_shadowing(block, klass, state) { + self.set_dynamic_send_reason(insn_id, SingletonClassWithShadowingMethodSeen); self.push_insn_id(block, insn_id); continue; } @@ -3305,8 +3305,8 @@ impl Function { self.push_insn_id(block, insn_id); continue; } // Check singleton class assumption first, before emitting other patchpoints - if !self.assume_no_singleton_classes(block, klass, state) { - self.set_dynamic_send_reason(insn_id, SingletonClassSeen); + if !self.assume_no_singleton_class_method_shadowing(block, klass, state) { + self.set_dynamic_send_reason(insn_id, SingletonClassWithShadowingMethodSeen); self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); @@ -3340,8 +3340,8 @@ impl Function { self.push_insn_id(block, insn_id); continue; }; // Check singleton class assumption first, before emitting other patchpoints - if !self.assume_no_singleton_classes(block, klass, state) { - self.set_dynamic_send_reason(insn_id, SingletonClassSeen); + if !self.assume_no_singleton_class_method_shadowing(block, klass, state) { + self.set_dynamic_send_reason(insn_id, SingletonClassWithShadowingMethodSeen); self.push_insn_id(block, insn_id); continue; } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); @@ -3416,7 +3416,7 @@ impl Function { }; if recv_type.is_string() { - self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_type.class() }, state }); + self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClassWithShadowingMethod { klass: recv_type.class() }, state }); let guard = self.push_insn(block, Insn::GuardType { val, guard_type: types::String, state }); // Infer type so AnyToString can fold off this self.insn_types[guard.0] = self.infer_type(guard); @@ -4133,8 +4133,8 @@ impl Function { } // Check singleton class assumption first, before emitting other patchpoints - if !fun.assume_no_singleton_classes(block, recv_class, state) { - fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); + if !fun.assume_no_singleton_class_method_shadowing(block, recv_class, state) { + fun.set_dynamic_send_reason(send_insn_id, SingletonClassWithShadowingMethodSeen); return Err(()); } @@ -4172,8 +4172,8 @@ impl Function { // func(int argc, VALUE *argv, VALUE recv) // Check singleton class assumption first, before emitting other patchpoints - if !fun.assume_no_singleton_classes(block, recv_class, state) { - fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); + if !fun.assume_no_singleton_class_method_shadowing(block, recv_class, state) { + fun.set_dynamic_send_reason(send_insn_id, SingletonClassWithShadowingMethodSeen); return Err(()); } @@ -4290,8 +4290,8 @@ impl Function { } // Check singleton class assumption first, before emitting other patchpoints - if !fun.assume_no_singleton_classes(block, recv_class, state) { - fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); + if !fun.assume_no_singleton_class_method_shadowing(block, recv_class, state) { + fun.set_dynamic_send_reason(send_insn_id, SingletonClassWithShadowingMethodSeen); return Err(()); } @@ -4372,8 +4372,8 @@ impl Function { return Err(()); } else { // Check singleton class assumption first, before emitting other patchpoints - if !fun.assume_no_singleton_classes(block, recv_class, state) { - fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen); + if !fun.assume_no_singleton_class_method_shadowing(block, recv_class, state) { + fun.set_dynamic_send_reason(send_insn_id, SingletonClassWithShadowingMethodSeen); return Err(()); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 202d92b833e0d4..1779025fe701c0 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -879,7 +879,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :object@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(CustomEq@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(CustomEq@0x1000) PatchPoint MethodRedefined(CustomEq@0x1000, !=@0x1008, cme:0x1010) v29:HeapObject[class_exact:CustomEq] = GuardType v9, HeapObject[class_exact:CustomEq] v30:BoolExact = CCallWithFrame v29, :BasicObject#!=@0x1038, v9 @@ -1032,7 +1032,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) @@ -1061,7 +1061,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, baz@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -1090,7 +1090,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, baz@0x1008, cme:0x1010) v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_cfunc_optimized_send_count @@ -1129,7 +1129,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v11:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048), v11 @@ -1160,7 +1160,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, fun_new_map@0x1008, cme:0x1010) v23:ArraySubclass[class_exact:C] = GuardType v9, ArraySubclass[class_exact:C] v24:BasicObject = SendDirect v23, 0x1038, :fun_new_map (0x1048) @@ -1194,7 +1194,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, bar@0x1008, cme:0x1010) v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:BasicObject = CCallWithFrame v24, :Enumerable#bar@0x1038, block=0x1040 @@ -1228,7 +1228,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :a@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) v23:ArrayExact = GuardType v9, ArrayExact v24:BasicObject = CCallWithFrame v23, :Array#length@0x1038 @@ -1286,7 +1286,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) @@ -1315,7 +1315,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v11:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, Integer@0x1008, cme:0x1010) v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :Integer (0x1048), v11 @@ -1346,7 +1346,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v11:Fixnum[1] = Const Value(1) v13:Fixnum[2] = Const Value(2) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 @@ -1377,11 +1377,11 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v25:BasicObject = SendDirect v24, 0x1038, :foo (0x1048) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, bar@0x1050, cme:0x1058) v28:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v29:BasicObject = SendDirect v28, 0x1038, :bar (0x1048) @@ -1408,7 +1408,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) @@ -1436,7 +1436,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v11:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048), v11 @@ -1465,7 +1465,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v11:Fixnum[3] = Const Value(3) v13:Fixnum[4] = Const Value(4) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 @@ -1493,14 +1493,14 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v45:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v46:BasicObject = SendDirect v45, 0x1038, :target (0x1048) v14:Fixnum[10] = Const Value(10) v16:Fixnum[20] = Const Value(20) v18:Fixnum[30] = Const Value(30) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v49:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v50:BasicObject = SendDirect v49, 0x1038, :target (0x1048), v14, v16, v18 @@ -1537,7 +1537,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v11:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v12:StringExact = StringCopy v11 - PatchPoint NoSingletonClass(Object@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1008) PatchPoint MethodRedefined(Object@0x1008, puts@0x1010, cme:0x1018) v23:HeapObject[class_exact*:Object@VALUE(0x1008)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1008)] v24:BasicObject = CCallVariadic v23, :Kernel#puts@0x1040, v12 @@ -2098,7 +2098,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[0] = Const Value(0) - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) v26:ArrayExact = GuardType v9, ArrayExact v27:CInt64[0] = UnboxFixnum v14 @@ -2135,7 +2135,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[0] = Const Value(0) - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) v26:HashExact = GuardType v9, HashExact v27:BasicObject = HashAref v26, v14 @@ -2898,7 +2898,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:ArrayExact = NewArray - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -2928,7 +2928,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:NilClass): v13:ArrayExact = NewArray - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, itself@0x1008, cme:0x1010) IncrCounter inline_cfunc_optimized_send_count PatchPoint NoEPEscape(test) @@ -2964,7 +2964,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, M) v29:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(Module@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Module@0x1010) PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020) IncrCounter inline_cfunc_optimized_send_count v34:StringExact|NilClass = CCall v29, :Module#name@0x1048 @@ -2995,7 +2995,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:ArrayExact = NewArray - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) IncrCounter inline_cfunc_optimized_send_count v17:Fixnum[5] = Const Value(5) @@ -3137,7 +3137,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:ArrayExact = NewArray - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) IncrCounter inline_cfunc_optimized_send_count v17:Fixnum[5] = Const Value(5) @@ -3190,7 +3190,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v21:CPtr = GetLEP @@ -3218,7 +3218,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) v20:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v21:FalseClass = Const Value(false) @@ -3248,7 +3248,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, block_given?@0x1008, cme:0x1010) v24:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_cfunc_optimized_send_count @@ -3310,7 +3310,7 @@ mod hir_opt_tests { bb3(v10:BasicObject, v11:BasicObject, v12:NilClass): v16:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v17:ArrayExact = ArrayDup v16 - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) IncrCounter inline_iseq_optimized_send_count v32:BasicObject = InvokeBuiltin leaf , v17 @@ -3341,7 +3341,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, M) v20:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(Module@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Module@0x1010) PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) IncrCounter inline_iseq_optimized_send_count v26:Class[Module@0x1010] = Const Value(VALUE(0x1010)) @@ -3377,7 +3377,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :c@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v23:BasicObject = SendDirect v22, 0x1038, :foo (0x1048) @@ -3408,7 +3408,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v11:Fixnum[1] = Const Value(1) v13:Fixnum[2] = Const Value(2) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 @@ -3443,7 +3443,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:NilClass): v13:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v32:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v8, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -3528,7 +3528,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v11:Fixnum[1] = Const Value(1) v13:Fixnum[2] = Const Value(2) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 @@ -3559,7 +3559,7 @@ mod hir_opt_tests { v11:Fixnum[3] = Const Value(3) v13:Fixnum[1] = Const Value(1) v15:Fixnum[2] = Const Value(2) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v25:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v27:BasicObject = SendDirect v25, 0x1038, :foo (0x1048), v13, v15, v11 @@ -3590,7 +3590,7 @@ mod hir_opt_tests { v11:Fixnum[0] = Const Value(0) v13:Fixnum[2] = Const Value(2) v15:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v25:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v27:BasicObject = SendDirect v25, 0x1038, :foo (0x1048), v11, v15, v13 @@ -3620,7 +3620,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v11:Fixnum[0] = Const Value(0) v13:Fixnum[2] = Const Value(2) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v24:BasicObject = SendDirect v23, 0x1038, :foo (0x1048), v11, v13 @@ -3651,7 +3651,7 @@ mod hir_opt_tests { v11:Fixnum[1] = Const Value(1) v13:Fixnum[3] = Const Value(3) v15:Fixnum[4] = Const Value(4) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v38:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v39:BasicObject = SendDirect v38, 0x1038, :foo (0x1048), v11, v13, v15 @@ -3659,7 +3659,7 @@ mod hir_opt_tests { v22:Fixnum[2] = Const Value(2) v24:Fixnum[4] = Const Value(4) v26:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v42:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v44:BasicObject = SendDirect v42, 0x1038, :foo (0x1048), v20, v22, v26, v24 @@ -3690,7 +3690,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v11:Fixnum[1] = Const Value(1) v13:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v36:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v37:Fixnum[4] = Const Value(4) @@ -3699,7 +3699,7 @@ mod hir_opt_tests { v20:Fixnum[2] = Const Value(2) v22:Fixnum[40] = Const Value(40) v24:Fixnum[30] = Const Value(30) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v42:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v44:BasicObject = SendDirect v42, 0x1038, :foo (0x1048), v18, v20, v24, v22 @@ -3729,7 +3729,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v11:Fixnum[6] = Const Value(6) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v49:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v50:BasicObject = SendDirect v49, 0x1038, :target (0x1048), v11 @@ -3737,7 +3737,7 @@ mod hir_opt_tests { v18:Fixnum[20] = Const Value(20) v20:Fixnum[30] = Const Value(30) v22:Fixnum[6] = Const Value(6) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, target@0x1008, cme:0x1010) v53:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v54:BasicObject = SendDirect v53, 0x1038, :target (0x1048), v16, v18, v20, v22 @@ -3774,7 +3774,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v11:Fixnum[2] = Const Value(2) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v22:BasicObject = SendDirect v21, 0x1038, :foo (0x1048), v11 @@ -3829,7 +3829,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v20:Fixnum[1] = Const Value(1) @@ -4085,7 +4085,7 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) v46:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count @@ -4124,7 +4124,7 @@ mod hir_opt_tests { v15:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010) v49:HeapObject[class_exact:C] = ObjectAllocClass C:VALUE(0x1008) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040) v52:BasicObject = SendDirect v49, 0x1068, :initialize (0x1078), v15 CheckInterrupts @@ -4156,7 +4156,7 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Object@0x1008, new@0x1009, cme:0x1010) v46:ObjectExact = ObjectAllocClass Object:VALUE(0x1008) - PatchPoint NoSingletonClass(Object@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1008) PatchPoint MethodRedefined(Object@0x1008, initialize@0x1038, cme:0x1040) v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count @@ -4189,7 +4189,7 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1009, cme:0x1010) v46:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008) - PatchPoint NoSingletonClass(BasicObject@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(BasicObject@0x1008) PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1038, cme:0x1040) v50:NilClass = Const Value(nil) IncrCounter inline_cfunc_optimized_send_count @@ -4254,7 +4254,7 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) v15:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Array@0x1008, new@0x1009, cme:0x1010) - PatchPoint NoSingletonClass(Class@0x1038) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1038) PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) v57:BasicObject = CCallVariadic v46, :Array.new@0x1040, v15 CheckInterrupts @@ -4285,7 +4285,7 @@ mod hir_opt_tests { v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(Set@0x1008, new@0x1009, cme:0x1010) v17:HeapBasicObject = ObjectAlloc v43 - PatchPoint NoSingletonClass(Set@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Set@0x1008) PatchPoint MethodRedefined(Set@0x1008, initialize@0x1038, cme:0x1040) v49:SetExact = GuardType v17, SetExact v50:BasicObject = CCallVariadic v49, :Set#initialize@0x1068 @@ -4317,7 +4317,7 @@ mod hir_opt_tests { v43:Class[String@0x1008] = Const Value(VALUE(0x1008)) v12:NilClass = Const Value(nil) PatchPoint MethodRedefined(String@0x1008, new@0x1009, cme:0x1010) - PatchPoint NoSingletonClass(Class@0x1038) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1038) PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010) v54:BasicObject = CCallVariadic v43, :String.new@0x1040 CheckInterrupts @@ -4350,7 +4350,7 @@ mod hir_opt_tests { v16:StringExact = StringCopy v15 PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020) v50:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008) - PatchPoint NoSingletonClass(Regexp@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Regexp@0x1008) PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050) v54:BasicObject = CCallVariadic v50, :Regexp#initialize@0x1078, v16 CheckInterrupts @@ -4380,7 +4380,7 @@ mod hir_opt_tests { Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): v18:ArrayExact = NewArray v11, v12 - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) v30:CInt64 = ArrayLength v18 v31:Fixnum = BoxFixnum v30 @@ -4411,7 +4411,7 @@ mod hir_opt_tests { Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): v18:ArrayExact = NewArray v11, v12 - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) v30:CInt64 = ArrayLength v18 v31:Fixnum = BoxFixnum v30 @@ -4643,7 +4643,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Proc@0x1000) PatchPoint MethodRedefined(Proc@0x1000, call@0x1008, cme:0x1010) v24:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] v25:BasicObject = InvokeProc v24, v14 @@ -4675,7 +4675,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[2] = Const Value(2) - PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Proc@0x1000) PatchPoint MethodRedefined(Proc@0x1000, []@0x1008, cme:0x1010) v25:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] v26:BasicObject = InvokeProc v25, v14 @@ -4707,7 +4707,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Proc@0x1000) PatchPoint MethodRedefined(Proc@0x1000, yield@0x1008, cme:0x1010) v24:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] v25:BasicObject = InvokeProc v24, v14 @@ -4739,7 +4739,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Proc@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Proc@0x1000) PatchPoint MethodRedefined(Proc@0x1000, ===@0x1008, cme:0x1010) v24:HeapObject[class_exact:Proc] = GuardType v9, HeapObject[class_exact:Proc] v25:BasicObject = InvokeProc v24, v14 @@ -5233,7 +5233,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:HashExact = NewHash - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, dup@0x1008, cme:0x1010) v23:BasicObject = CCallWithFrame v10, :Kernel#dup@0x1038 v14:BasicObject = Send v23, :freeze # SendFallbackReason: Uncategorized(opt_send_without_block) @@ -5329,7 +5329,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:ArrayExact = NewArray - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, dup@0x1008, cme:0x1010) v23:BasicObject = CCallWithFrame v10, :Kernel#dup@0x1038 v14:BasicObject = Send v23, :freeze # SendFallbackReason: Uncategorized(opt_send_without_block) @@ -5426,7 +5426,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:StringExact = StringCopy v10 - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) v24:BasicObject = CCallWithFrame v11, :String#dup@0x1040 v15:BasicObject = Send v24, :freeze # SendFallbackReason: Uncategorized(opt_send_without_block) @@ -5525,7 +5525,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:StringExact = StringCopy v10 - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) PatchPoint MethodRedefined(String@0x1008, dup@0x1010, cme:0x1018) v24:BasicObject = CCallWithFrame v11, :String#dup@0x1040 v15:BasicObject = Send v24, :-@ # SendFallbackReason: Uncategorized(opt_send_without_block) @@ -5608,7 +5608,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) v28:String = GuardType v9, String v21:StringExact = StringConcat v13, v28 CheckInterrupts @@ -5642,7 +5642,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(MyString@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(MyString@0x1008) v28:String = GuardType v9, String v21:StringExact = StringConcat v13, v28 CheckInterrupts @@ -5674,7 +5674,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v27:ArrayExact = GuardType v9, ArrayExact - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, to_s@0x1010, cme:0x1018) v32:BasicObject = CCallWithFrame v27, :Array#to_s@0x1040 v19:String = AnyToString v9, str: v32 @@ -5768,7 +5768,7 @@ mod hir_opt_tests { PatchPoint StableConstantNames(0x1000, S) v23:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v12:Fixnum[0] = Const Value(0) - PatchPoint NoSingletonClass(Array@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1010) PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020) v27:CInt64[0] = UnboxFixnum v12 v28:CInt64 = ArrayLength v23 @@ -5803,7 +5803,7 @@ mod hir_opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v11:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) v25:CInt64[1] = UnboxFixnum v13 v26:CInt64 = ArrayLength v11 @@ -5836,7 +5836,7 @@ mod hir_opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v11:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[-3] = Const Value(-3) - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) v25:CInt64[-3] = UnboxFixnum v13 v26:CInt64 = ArrayLength v11 @@ -5869,7 +5869,7 @@ mod hir_opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v11:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[-10] = Const Value(-10) - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) v25:CInt64[-10] = UnboxFixnum v13 v26:CInt64 = ArrayLength v11 @@ -5902,7 +5902,7 @@ mod hir_opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v11:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[10] = Const Value(10) - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) v25:CInt64[10] = UnboxFixnum v13 v26:CInt64 = ArrayLength v11 @@ -5938,7 +5938,7 @@ mod hir_opt_tests { PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) v11:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:Fixnum[10] = Const Value(10) - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) v24:BasicObject = SendDirect v11, 0x1040, :[] (0x1050), v13 CheckInterrupts @@ -5999,7 +5999,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:ArrayExact = ArrayDup v10 - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, max@0x1010, cme:0x1018) v21:BasicObject = SendDirect v11, 0x1040, :max (0x1050) CheckInterrupts @@ -6079,7 +6079,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, zero@0x1008, cme:0x1010) v23:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -6167,7 +6167,7 @@ mod hir_opt_tests { PatchPoint StableConstantNames(0x1000, Foo) v22:Class[Foo@0x1008] = Const Value(VALUE(0x1008)) v12:Fixnum[100] = Const Value(100) - PatchPoint NoSingletonClass(Class@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020) IncrCounter inline_iseq_optimized_send_count CheckInterrupts @@ -6475,7 +6475,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :val@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, nil?@0x1008, cme:0x1010) v24:StringExact = GuardType v9, StringExact v25:FalseClass = Const Value(false) @@ -6505,7 +6505,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :a@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, !@0x1008, cme:0x1010) v24:ArrayExact = GuardType v9, ArrayExact v25:FalseClass = Const Value(false) @@ -6637,7 +6637,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :a@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, empty?@0x1008, cme:0x1010) v24:ArrayExact = GuardType v9, ArrayExact v25:CInt64 = ArrayLength v24 @@ -6670,7 +6670,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :a@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, empty?@0x1008, cme:0x1010) v24:HashExact = GuardType v9, HashExact IncrCounter inline_cfunc_optimized_send_count @@ -6703,7 +6703,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :b@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) v28:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v29:CBool = IsBitEqual v28, v12 @@ -6798,7 +6798,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -6836,7 +6836,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:CShape = LoadField v22, :_shape_id@0x1038 @@ -6878,7 +6878,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:CShape = LoadField v22, :_shape_id@0x1038 @@ -7087,14 +7087,14 @@ mod hir_opt_tests { Jump bb4(v8, v9, v32) bb5(v15:BasicObject, v16:BasicObject, v17:BasicObject): v19:HeapObject[class_exact:C] = RefineType v17, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) IncrCounter getivar_fallback_not_monomorphic v45:BasicObject = GetIvar v19, :@foo Jump bb4(v15, v16, v45) bb6(v24:BasicObject, v25:BasicObject, v26:BasicObject): v28:HeapObject[class_exact:C] = RefineType v26, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) IncrCounter getivar_fallback_not_monomorphic v48:BasicObject = GetIvar v28, :@foo @@ -7134,7 +7134,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] IncrCounter getivar_fallback_too_complex @@ -7163,7 +7163,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:ArrayExact = ArrayDup v10 - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, map@0x1010, cme:0x1018) v21:BasicObject = SendDirect v11, 0x1040, :map (0x1050) CheckInterrupts @@ -7204,7 +7204,7 @@ mod hir_opt_tests { v35:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint StableConstantNames(0x1010, B) v38:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) - PatchPoint NoSingletonClass(Array@0x1020) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1020) PatchPoint MethodRedefined(Array@0x1020, zip@0x1028, cme:0x1030) v42:BasicObject = CCallVariadic v35, :zip@0x1058, v38 v22:BasicObject = GetLocal :result, l0, EP@3 @@ -7337,7 +7337,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] v20:BasicObject = SendDirect v19, 0x1038, :foo (0x1048) @@ -7372,7 +7372,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, O) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(C@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) v25:CShape = LoadField v20, :_shape_id@0x1048 v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) @@ -7408,7 +7408,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, O) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(C@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) v25:CShape = LoadField v20, :_shape_id@0x1048 v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) @@ -7442,7 +7442,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:CShape = LoadField v22, :_shape_id@0x1038 @@ -7477,7 +7477,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v25:CShape = LoadField v22, :_shape_id@0x1038 @@ -7585,7 +7585,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v23:BasicObject = LoadField v22, :foo@0x1038 @@ -7615,7 +7615,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v23:CPtr = LoadField v22, :_as_heap@0x1038 @@ -7649,7 +7649,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v18:Fixnum[5] = Const Value(5) @@ -7682,7 +7682,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :v@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v30:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v31:CUInt64 = LoadField v30, :_rbasic_flags@0x1038 @@ -7718,7 +7718,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :v@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v30:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v31:CUInt64 = LoadField v30, :_rbasic_flags@0x1038 @@ -7748,7 +7748,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:ArrayExact = NewArray - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) v21:ArrayExact = CCallWithFrame v10, :Array#reverse@0x1038 CheckInterrupts @@ -7776,7 +7776,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v10:ArrayExact = NewArray - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, reverse@0x1008, cme:0x1010) v16:Fixnum[5] = Const Value(5) CheckInterrupts @@ -7803,7 +7803,7 @@ mod hir_opt_tests { v10:ArrayExact = NewArray v12:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v13:StringExact = StringCopy v12 - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, join@0x1010, cme:0x1018) v24:StringExact = CCallVariadic v10, :Array#join@0x1040, v13 CheckInterrupts @@ -7829,7 +7829,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:StringExact = StringCopy v10 - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -7855,7 +7855,7 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:StringExact = StringCopy v10 - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) PatchPoint MethodRedefined(String@0x1008, to_s@0x1010, cme:0x1018) IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -7882,7 +7882,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, to_s@0x1008, cme:0x1010) v23:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -7998,7 +7998,7 @@ mod hir_opt_tests { v13:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v14:ArrayExact = ArrayDup v13 v19:Fixnum[0] = Const Value(0) - PatchPoint NoSingletonClass(Array@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1008) PatchPoint MethodRedefined(Array@0x1008, []@0x1010, cme:0x1018) v31:CInt64[0] = UnboxFixnum v19 v32:CInt64 = ArrayLength v14 @@ -8035,7 +8035,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :idx@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, []@0x1008, cme:0x1010) v28:ArrayExact = GuardType v11, ArrayExact v29:Fixnum = GuardType v12, Fixnum @@ -8075,7 +8075,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :idx@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) v28:ArraySubclass[class_exact:C] = GuardType v11, ArraySubclass[class_exact:C] v29:Fixnum = GuardType v12, Fixnum @@ -8115,7 +8115,7 @@ mod hir_opt_tests { v13:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v14:HashExact = HashDup v13 v19:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Hash@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1008) PatchPoint MethodRedefined(Hash@0x1008, []@0x1010, cme:0x1018) v31:BasicObject = HashAref v14, v19 IncrCounter inline_cfunc_optimized_send_count @@ -8147,7 +8147,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :key@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, []@0x1008, cme:0x1010) v28:HashExact = GuardType v11, HashExact v29:BasicObject = HashAref v28, v12 @@ -8181,7 +8181,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :key@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, []@0x1008, cme:0x1010) v28:HashSubclass[class_exact:C] = GuardType v11, HashSubclass[class_exact:C] v29:BasicObject = CCallWithFrame v28, :Hash#[]@0x1038, v12 @@ -8212,7 +8212,7 @@ mod hir_opt_tests { PatchPoint StableConstantNames(0x1000, H) v23:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v12:StaticSymbol[:a] = Const Value(VALUE(0x1010)) - PatchPoint NoSingletonClass(Hash@0x1018) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1018) PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028) v27:BasicObject = HashAref v23, v12 IncrCounter inline_cfunc_optimized_send_count @@ -8246,7 +8246,7 @@ mod hir_opt_tests { PatchPoint NoEPEscape(test) v22:Fixnum[1] = Const Value(1) v24:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, []=@0x1008, cme:0x1010) HashAset v13, v22, v24 IncrCounter inline_cfunc_optimized_send_count @@ -8280,7 +8280,7 @@ mod hir_opt_tests { v10:BasicObject = LoadArg :val@3 Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, []=@0x1008, cme:0x1010) v36:HashExact = GuardType v13, HashExact HashAset v36, v14, v15 @@ -8316,7 +8316,7 @@ mod hir_opt_tests { v10:BasicObject = LoadArg :val@3 Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, []=@0x1008, cme:0x1010) v36:HashSubclass[class_exact:C] = GuardType v13, HashSubclass[class_exact:C] v37:BasicObject = CCallWithFrame v36, :Hash#[]=@0x1038, v14, v15 @@ -8345,7 +8345,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Thread) v20:Class[Thread@0x1008] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(Class@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020) v24:CPtr = LoadEC v25:CPtr = LoadField v24, :thread_ptr@0x1048 @@ -8379,7 +8379,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): v16:Fixnum[1] = Const Value(1) v18:Fixnum[10] = Const Value(10) - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) v32:ArrayExact = GuardType v9, ArrayExact v33:CUInt64 = LoadField v32, :_rbasic_flags@0x1038 @@ -8424,7 +8424,7 @@ mod hir_opt_tests { v10:BasicObject = LoadArg :val@3 Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) v36:ArrayExact = GuardType v13, ArrayExact v37:Fixnum = GuardType v14, Fixnum @@ -8472,7 +8472,7 @@ mod hir_opt_tests { v10:BasicObject = LoadArg :val@3 Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - PatchPoint NoSingletonClass(MyArray@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(MyArray@0x1000) PatchPoint MethodRedefined(MyArray@0x1000, []=@0x1008, cme:0x1010) v36:ArraySubclass[class_exact:MyArray] = GuardType v13, ArraySubclass[class_exact:MyArray] v37:BasicObject = CCallVariadic v36, :Array#[]=@0x1038, v14, v15 @@ -8503,7 +8503,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) v26:ArrayExact = GuardType v9, ArrayExact ArrayPush v26, v14 @@ -8535,7 +8535,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:Fixnum[1] = Const Value(1) - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) v25:ArrayExact = GuardType v9, ArrayExact ArrayPush v25, v14 @@ -8569,7 +8569,7 @@ mod hir_opt_tests { v14:Fixnum[1] = Const Value(1) v16:Fixnum[2] = Const Value(2) v18:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) v29:ArrayExact = GuardType v9, ArrayExact v30:BasicObject = CCallVariadic v29, :Array#push@0x1038, v14, v16, v18 @@ -8756,7 +8756,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :arr@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, length@0x1008, cme:0x1010) v24:ArrayExact = GuardType v9, ArrayExact v25:CInt64 = ArrayLength v24 @@ -8787,7 +8787,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :arr@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Array@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Array@0x1000) PatchPoint MethodRedefined(Array@0x1000, size@0x1008, cme:0x1010) v24:ArrayExact = GuardType v9, ArrayExact v25:CInt64 = ArrayLength v24 @@ -8819,7 +8819,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) PatchPoint MethodRedefined(String@0x1008, =~@0x1010, cme:0x1018) v26:StringExact = GuardType v9, StringExact v27:BasicObject = CCallWithFrame v26, :String#=~@0x1040, v14 @@ -8849,7 +8849,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :i@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) v27:StringExact = GuardType v11, StringExact v28:Fixnum = GuardType v12, Fixnum @@ -8889,7 +8889,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :i@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, getbyte@0x1008, cme:0x1010) v31:StringExact = GuardType v11, StringExact v32:Fixnum = GuardType v12, Fixnum @@ -8930,7 +8930,7 @@ mod hir_opt_tests { v10:BasicObject = LoadArg :val@3 Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, setbyte@0x1008, cme:0x1010) v31:StringExact = GuardType v13, StringExact v32:Fixnum = GuardType v14, Fixnum @@ -8976,7 +8976,7 @@ mod hir_opt_tests { v10:BasicObject = LoadArg :val@3 Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - PatchPoint NoSingletonClass(MyString@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(MyString@0x1000) PatchPoint MethodRedefined(MyString@0x1000, setbyte@0x1008, cme:0x1010) v31:StringSubclass[class_exact:MyString] = GuardType v13, StringSubclass[class_exact:MyString] v32:Fixnum = GuardType v14, Fixnum @@ -9020,7 +9020,7 @@ mod hir_opt_tests { v10:BasicObject = LoadArg :val@3 Jump bb3(v7, v8, v9, v10) bb3(v12:BasicObject, v13:BasicObject, v14:BasicObject, v15:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, setbyte@0x1008, cme:0x1010) v31:StringExact = GuardType v13, StringExact v32:BasicObject = CCallWithFrame v31, :String#setbyte@0x1038, v14, v15 @@ -9050,7 +9050,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) v24:StringExact = GuardType v9, StringExact v25:CInt64 = LoadField v24, :len@0x1038 @@ -9085,7 +9085,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, empty?@0x1008, cme:0x1010) v28:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -9406,7 +9406,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :y@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) v28:StringExact = GuardType v11, StringExact v29:String = GuardType v12, String @@ -9438,7 +9438,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :y@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) v28:StringExact = GuardType v11, StringExact v29:Fixnum = GuardType v12, Fixnum @@ -9472,7 +9472,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :y@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, <<@0x1008, cme:0x1010) v28:StringExact = GuardType v11, StringExact v29:String = GuardType v12, String @@ -9506,7 +9506,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :y@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(MyString@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(MyString@0x1000) PatchPoint MethodRedefined(MyString@0x1000, <<@0x1008, cme:0x1010) v28:StringSubclass[class_exact:MyString] = GuardType v11, StringSubclass[class_exact:MyString] v29:BasicObject = CCallWithFrame v28, :String#<<@0x1038, v12 @@ -9534,7 +9534,7 @@ mod hir_opt_tests { v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v11:StringExact = StringCopy v10 v13:StaticSymbol[:a] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1010) PatchPoint MethodRedefined(String@0x1010, <<@0x1018, cme:0x1020) v25:BasicObject = CCallWithFrame v11, :String#<<@0x1048, v13 CheckInterrupts @@ -9588,7 +9588,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :x@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, ascii_only?@0x1008, cme:0x1010) v23:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -9837,7 +9837,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :hash@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) v24:HashExact = GuardType v9, HashExact IncrCounter inline_cfunc_optimized_send_count @@ -9869,7 +9869,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :hash@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(Hash@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Hash@0x1000) PatchPoint MethodRedefined(Hash@0x1000, size@0x1008, cme:0x1010) v28:HashExact = GuardType v9, HashExact IncrCounter inline_cfunc_optimized_send_count @@ -9902,7 +9902,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) @@ -9935,7 +9935,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) @@ -9971,7 +9971,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) @@ -10007,7 +10007,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) v16:FalseClass = Const Value(false) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) @@ -10043,7 +10043,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) v16:NilClass = Const Value(nil) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) @@ -10079,7 +10079,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) v16:TrueClass = Const Value(true) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) @@ -10114,7 +10114,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) v16:Fixnum[4] = Const Value(4) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) @@ -10149,7 +10149,7 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) v16:NilClass = Const Value(nil) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v27:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, foo@0x1040, cme:0x1048) @@ -10182,7 +10182,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] PatchPoint MethodRedefined(C@0x1008, respond_to_missing?@0x1040, cme:0x1048) @@ -10219,7 +10219,7 @@ mod hir_opt_tests { Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): v14:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) - PatchPoint NoSingletonClass(C@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1008) PatchPoint MethodRedefined(C@0x1008, respond_to?@0x1010, cme:0x1018) v25:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] v26:BasicObject = CCallVariadic v25, :Kernel#respond_to?@0x1040, v14 @@ -10246,7 +10246,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10274,7 +10274,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10302,7 +10302,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10330,7 +10330,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10358,7 +10358,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10386,7 +10386,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10414,7 +10414,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10443,7 +10443,7 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): v11:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v21:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10473,7 +10473,7 @@ mod hir_opt_tests { v11:Fixnum[1] = Const Value(1) v13:Fixnum[2] = Const Value(2) v15:Fixnum[3] = Const Value(3) - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v25:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10603,7 +10603,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10634,7 +10634,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10665,7 +10665,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, callee@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -10724,7 +10724,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :r@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) v28:StringExact = GuardType v11, StringExact v29:String = GuardType v12, String @@ -10758,7 +10758,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :r@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, ==@0x1008, cme:0x1010) v28:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] v29:String = GuardType v12, String @@ -10792,7 +10792,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :r@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, ==@0x1008, cme:0x1010) v28:StringExact = GuardType v11, StringExact v29:String = GuardType v12, String @@ -10824,7 +10824,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :r@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) v27:StringExact = GuardType v11, StringExact v28:String = GuardType v12, String @@ -10858,7 +10858,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :r@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, ===@0x1008, cme:0x1010) v27:StringSubclass[class_exact:C] = GuardType v11, StringSubclass[class_exact:C] v28:String = GuardType v12, String @@ -10892,7 +10892,7 @@ mod hir_opt_tests { v8:BasicObject = LoadArg :r@2 Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, ===@0x1008, cme:0x1010) v27:StringExact = GuardType v11, StringExact v28:String = GuardType v12, String @@ -10924,7 +10924,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) v24:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -10956,7 +10956,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, size@0x1008, cme:0x1010) v28:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -10987,7 +10987,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) v23:StringExact = GuardType v9, StringExact v24:CInt64 = LoadField v23, :len@0x1038 @@ -11020,7 +11020,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, bytesize@0x1008, cme:0x1010) v27:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -11051,7 +11051,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) v24:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -11084,7 +11084,7 @@ mod hir_opt_tests { PatchPoint StableConstantNames(0x1000, String) v26:Class[String@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) - PatchPoint NoSingletonClass(Class@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) v30:BoolExact = IsA v9, v26 IncrCounter inline_cfunc_optimized_send_count @@ -11116,7 +11116,7 @@ mod hir_opt_tests { PatchPoint StableConstantNames(0x1000, Kernel) v26:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) - PatchPoint NoSingletonClass(Module@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Module@0x1010) PatchPoint MethodRedefined(Module@0x1010, ===@0x1018, cme:0x1020) IncrCounter inline_cfunc_optimized_send_count v31:BoolExact = CCall v26, :Module#===@0x1048, v9 @@ -11147,7 +11147,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) v24:Class[String@0x1008] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) PatchPoint MethodRedefined(String@0x1008, is_a?@0x1009, cme:0x1010) v28:StringExact = GuardType v9, StringExact v29:BoolExact = IsA v28, v24 @@ -11179,7 +11179,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Kernel) v24:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1010) PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) v28:StringExact = GuardType v9, StringExact v29:BasicObject = CCallWithFrame v28, :Kernel#is_a?@0x1048, v24 @@ -11213,7 +11213,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Integer) v28:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1010) PatchPoint MethodRedefined(String@0x1010, is_a?@0x1018, cme:0x1020) v32:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -11249,7 +11249,7 @@ mod hir_opt_tests { PatchPoint StableConstantNames(0x1000, Integer) v30:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) PatchPoint NoEPEscape(test) - PatchPoint NoSingletonClass(Class@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, ===@0x1018, cme:0x1020) IncrCounter inline_cfunc_optimized_send_count v22:Fixnum[5] = Const Value(5) @@ -11280,7 +11280,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, String) v24:Class[String@0x1008] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1008) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1008) PatchPoint MethodRedefined(String@0x1008, kind_of?@0x1009, cme:0x1010) v28:StringExact = GuardType v9, StringExact v29:BoolExact = IsA v28, v24 @@ -11312,7 +11312,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Kernel) v24:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1010) PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1018, cme:0x1020) v28:StringExact = GuardType v9, StringExact v29:BasicObject = CCallWithFrame v28, :Kernel#kind_of?@0x1048, v24 @@ -11346,7 +11346,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Integer) v28:Class[Integer@0x1008] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1010) PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1018, cme:0x1020) v32:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -11431,7 +11431,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(String@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) v28:StringExact = GuardType v9, StringExact IncrCounter inline_cfunc_optimized_send_count @@ -11472,7 +11472,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) v43:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] IncrCounter inline_iseq_optimized_send_count @@ -11480,7 +11480,7 @@ mod hir_opt_tests { IncrCounter inline_cfunc_optimized_send_count v13:StaticSymbol[:_lex_actions] = Const Value(VALUE(0x1038)) v15:TrueClass = Const Value(true) - PatchPoint NoSingletonClass(Class@0x1040) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1040) PatchPoint MethodRedefined(Class@0x1040, respond_to?@0x1048, cme:0x1050) PatchPoint MethodRedefined(Class@0x1040, _lex_actions@0x1078, cme:0x1080) v55:TrueClass = Const Value(true) @@ -11512,13 +11512,13 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) v24:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] IncrCounter inline_iseq_optimized_send_count v28:Class[C@0x1000] = Const Value(VALUE(0x1000)) IncrCounter inline_cfunc_optimized_send_count - PatchPoint NoSingletonClass(Class@0x1038) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1038) PatchPoint MethodRedefined(Class@0x1038, name@0x1040, cme:0x1048) IncrCounter inline_cfunc_optimized_send_count v34:StringExact|NilClass = CCall v28, :Module#name@0x1070 @@ -11547,7 +11547,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :o@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, class@0x1008, cme:0x1010) v22:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C] IncrCounter inline_iseq_optimized_send_count @@ -11602,7 +11602,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(Object@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1000) PatchPoint MethodRedefined(Object@0x1000, class@0x1008, cme:0x1010) v19:HeapObject[class_exact*:Object@VALUE(0x1000)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1000)] IncrCounter inline_iseq_optimized_send_count @@ -11662,7 +11662,7 @@ mod hir_opt_tests { v63:CShape[0x1003] = Const CShape(0x1003) StoreField v58, :_shape_id@0x1000, v63 v46:Class[VMFrozenCore] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(Class@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Class@0x1010) PatchPoint MethodRedefined(Class@0x1010, lambda@0x1018, cme:0x1020) v68:BasicObject = CCallWithFrame v46, :RubyVM::FrozenCore.lambda@0x1048, block=0x1050 v49:BasicObject = GetLocal :a, l0, EP@6 @@ -11705,7 +11705,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_OBJ) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestFrozen@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestFrozen@0x1010) PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020) v29:Fixnum[1] = Const Value(1) CheckInterrupts @@ -11746,7 +11746,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, MULTI_FROZEN) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestMultiIvars@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestMultiIvars@0x1010) PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020) v29:Fixnum[20] = Const Value(20) CheckInterrupts @@ -11785,7 +11785,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_STR) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestFrozenStr@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestFrozenStr@0x1010) PatchPoint MethodRedefined(TestFrozenStr@0x1010, name@0x1018, cme:0x1020) v29:StringExact[VALUE(0x1048)] = Const Value(VALUE(0x1048)) CheckInterrupts @@ -11824,7 +11824,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_NIL) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestFrozenNil@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestFrozenNil@0x1010) PatchPoint MethodRedefined(TestFrozenNil@0x1010, value@0x1018, cme:0x1020) v29:NilClass = Const Value(nil) CheckInterrupts @@ -11863,7 +11863,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, UNFROZEN_OBJ) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestUnfrozen@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestUnfrozen@0x1010) PatchPoint MethodRedefined(TestUnfrozen@0x1010, a@0x1018, cme:0x1020) v25:CShape = LoadField v20, :_shape_id@0x1048 v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) @@ -11904,7 +11904,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_READER) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestAttrReader@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestAttrReader@0x1010) PatchPoint MethodRedefined(TestAttrReader@0x1010, value@0x1018, cme:0x1020) v29:Fixnum[42] = Const Value(42) CheckInterrupts @@ -11943,7 +11943,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_SYM) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestFrozenSym@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestFrozenSym@0x1010) PatchPoint MethodRedefined(TestFrozenSym@0x1010, sym@0x1018, cme:0x1020) v29:StaticSymbol[:hello] = Const Value(VALUE(0x1048)) CheckInterrupts @@ -11982,7 +11982,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, FROZEN_TRUE) v20:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestFrozenBool@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestFrozenBool@0x1010) PatchPoint MethodRedefined(TestFrozenBool@0x1010, flag@0x1018, cme:0x1020) v29:TrueClass = Const Value(true) CheckInterrupts @@ -12019,7 +12019,7 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :obj@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - PatchPoint NoSingletonClass(TestDynamic@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(TestDynamic@0x1000) PatchPoint MethodRedefined(TestDynamic@0x1000, val@0x1008, cme:0x1010) v22:HeapObject[class_exact:TestDynamic] = GuardType v9, HeapObject[class_exact:TestDynamic] v25:CShape = LoadField v22, :_shape_id@0x1038 @@ -12062,7 +12062,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, NESTED_FROZEN) v27:HeapObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(TestNestedAccess@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(TestNestedAccess@0x1010) PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020) v52:Fixnum[100] = Const Value(100) PatchPoint StableConstantNames(0x1048, NESTED_FROZEN) @@ -12098,7 +12098,7 @@ mod hir_opt_tests { PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, S) v20:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - PatchPoint NoSingletonClass(String@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1010) PatchPoint MethodRedefined(String@0x1010, bytesize@0x1018, cme:0x1020) v24:CInt64 = LoadField v20, :len@0x1048 v25:Fixnum = BoxFixnum v24 @@ -12128,7 +12128,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) v19:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] IncrCounter inline_iseq_optimized_send_count @@ -12187,7 +12187,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(BasicObject@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(BasicObject@0x1000) PatchPoint MethodRedefined(BasicObject@0x1000, initialize@0x1008, cme:0x1010) v21:BasicObjectExact = GuardType v6, BasicObjectExact v22:NilClass = Const Value(nil) @@ -12272,7 +12272,7 @@ mod hir_opt_tests { v4:BasicObject = LoadArg :self@0 Jump bb3(v4) bb3(v6:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, secret@0x1008, cme:0x1010) v19:HeapObject[class_exact:C] = GuardType v6, HeapObject[class_exact:C] IncrCounter inline_iseq_optimized_send_count @@ -12313,18 +12313,17 @@ mod hir_opt_tests { } // Test that when a singleton class has been seen for a class, we skip the - // NoSingletonClass optimization to avoid an invalidation loop. + // Creating a singleton class alone should NOT disable optimization; only defining + // a method on the singleton class that shadows a method on the original class should. #[test] - fn test_skip_optimization_after_singleton_class_seen() { - // First, trigger the singleton class callback for String by creating a singleton class. - // This should mark String as having had a singleton class seen. + fn test_singleton_class_creation_alone_does_not_prevent_optimization() { + // Create a singleton class for a String instance, but don't define any methods on it. eval(r#" "hello".singleton_class "#); - // Now define and compile a method that would normally be optimized with NoSingletonClass. - // Since String has had a singleton class, the optimization should be skipped and we - // should fall back to SendWithoutBlock. + // Define and compile a method that calls String#length. + // Since no shadowing method was defined, the optimization should still apply. eval(r#" def test(s) s.length @@ -12332,7 +12331,51 @@ mod hir_opt_tests { test("asdf") "#); - // The output should NOT have NoSingletonClass patchpoint for String, and should + // The output should still have the optimized CCall path with the patchpoint, + // because merely creating a singleton class doesn't shadow any methods. + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :s, l0, SP@4 + Jump bb3(v1, v2) + bb2(): + EntryPoint JIT(0) + v5:BasicObject = LoadArg :self@0 + v6:BasicObject = LoadArg :s@1 + Jump bb3(v5, v6) + bb3(v8:BasicObject, v9:BasicObject): + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + v24:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v26:Fixnum = CCall v24, :String#length@0x1038 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_skip_optimization_after_singleton_class_shadowing_method() { + // Define a method on a singleton class that shadows String#length. + // This should mark String as having had a shadowing singleton method seen. + eval(r#" + s = "hello" + def s.length; 42; end + "#); + + // Now define and compile a method that would normally be optimized. + // Since a shadowing singleton method was defined, the optimization should be + // skipped and we should fall back to SendWithoutBlock. + eval(r#" + def test(s) + s.length + end + test("asdf") + "#); + + // The output should NOT have the patchpoint for String, and should // fall back to SendWithoutBlock instead of the optimized CCall path. assert_snapshot!(hir_string("test"), @r" fn test@:3: @@ -12347,12 +12390,133 @@ mod hir_opt_tests { v6:BasicObject = LoadArg :s@1 Jump bb3(v5, v6) bb3(v8:BasicObject, v9:BasicObject): - v15:BasicObject = Send v9, :length # SendFallbackReason: Singleton class previously created for receiver class + v15:BasicObject = Send v9, :length # SendFallbackReason: Singleton class with shadowing method previously seen for receiver class CheckInterrupts Return v15 "); } + #[test] + fn test_nested_singleton_class_does_not_prevent_optimization() { + // Access nested singleton classes without defining any shadowing methods. + eval(r#" + s = "hello" + s.singleton_class.singleton_class + "#); + + eval(r#" + def test(s) + s.length + end + test("asdf") + "#); + + // Nested singleton class access without shadowing methods should not + // prevent optimization. + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :s, l0, SP@4 + Jump bb3(v1, v2) + bb2(): + EntryPoint JIT(0) + v5:BasicObject = LoadArg :self@0 + v6:BasicObject = LoadArg :s@1 + Jump bb3(v5, v6) + bb3(v8:BasicObject, v9:BasicObject): + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + v24:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v26:Fixnum = CCall v24, :String#length@0x1038 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_nested_singleton_class_method_does_not_shadow_instance_method() { + // Define :length on the doubly-nested singleton (metaclass of s's singleton class). + // This is in the metaclass chain, NOT the instance method chain, so it doesn't + // shadow String#length for instance method calls. + eval(r#" + s = "hello" + s.singleton_class.singleton_class.define_method(:length) { 99 } + "#); + + eval(r#" + def test(s) + s.length + end + test("asdf") + "#); + + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :s, l0, SP@4 + Jump bb3(v1, v2) + bb2(): + EntryPoint JIT(0) + v5:BasicObject = LoadArg :self@0 + v6:BasicObject = LoadArg :s@1 + Jump bb3(v5, v6) + bb3(v8:BasicObject, v9:BasicObject): + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + v24:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v26:Fixnum = CCall v24, :String#length@0x1038 + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_singleton_class_non_shadowing_method_does_not_prevent_optimization() { + // Define a method on a singleton class that does NOT shadow any method on String. + eval(r#" + s = "hello" + def s.my_custom_method; 42; end + "#); + + // Define and compile a method that calls String#length. + // Since the singleton method doesn't shadow #length, optimization should still apply. + eval(r#" + def test(s) + s.length + end + test("asdf") + "#); + + // The output should still have the optimized CCall path with the patchpoint. + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal :s, l0, SP@4 + Jump bb3(v1, v2) + bb2(): + EntryPoint JIT(0) + v5:BasicObject = LoadArg :self@0 + v6:BasicObject = LoadArg :s@1 + Jump bb3(v5, v6) + bb3(v8:BasicObject, v9:BasicObject): + PatchPoint NoSingletonClassWithShadowingMethod(String@0x1000) + PatchPoint MethodRedefined(String@0x1000, length@0x1008, cme:0x1010) + v24:StringExact = GuardType v9, StringExact + IncrCounter inline_cfunc_optimized_send_count + v26:Fixnum = CCall v24, :String#length@0x1038 + CheckInterrupts + Return v26 + "); + } + #[test] fn test_invokesuper_to_iseq_optimizes_to_direct() { eval(" @@ -12725,7 +12889,7 @@ mod hir_opt_tests { v8:NilClass = Const Value(nil) Jump bb3(v6, v7, v8) bb3(v10:BasicObject, v11:BasicObject, v12:NilClass): - PatchPoint NoSingletonClass(B@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(B@0x1000) PatchPoint MethodRedefined(B@0x1000, proc@0x1008, cme:0x1010) v36:HeapObject[class_exact:B] = GuardType v10, HeapObject[class_exact:B] v37:BasicObject = CCallWithFrame v36, :Kernel#proc@0x1038, block=0x1040 @@ -12922,13 +13086,13 @@ mod hir_opt_tests { v32:BasicObject = Send v9, :foo # SendFallbackReason: SendWithoutBlock: polymorphic fallback Jump bb4(v8, v9, v32) bb5(v15:BasicObject, v16:BasicObject, v17:BasicObject): - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, foo@0x1008, cme:0x1010) IncrCounter inline_iseq_optimized_send_count v55:Fixnum[3] = Const Value(3) Jump bb4(v15, v16, v55) bb6(v24:BasicObject, v25:BasicObject, v26:BasicObject): - PatchPoint NoSingletonClass(D@0x1038) + PatchPoint NoSingletonClassWithShadowingMethod(D@0x1038) PatchPoint MethodRedefined(D@0x1038, foo@0x1008, cme:0x1040) IncrCounter inline_iseq_optimized_send_count v57:Fixnum[4] = Const Value(4) @@ -12977,7 +13141,7 @@ mod hir_opt_tests { Jump bb4(v8, v9, v32) bb5(v15:BasicObject, v16:BasicObject, v17:BasicObject): v19:HeapObject[class_exact:C] = RefineType v17, HeapObject[class_exact:C] - PatchPoint NoSingletonClass(C@0x1000) + PatchPoint NoSingletonClassWithShadowingMethod(C@0x1000) PatchPoint MethodRedefined(C@0x1000, itself@0x1008, cme:0x1010) IncrCounter inline_cfunc_optimized_send_count Jump bb4(v15, v16, v19) @@ -13127,7 +13291,7 @@ mod hir_opt_tests { v68:Truthy = RefineType v59, Truthy IfTrue v67, bb8(v57, v58, v68, v60, v61) v70:Falsy = RefineType v59, Falsy - PatchPoint NoSingletonClass(Object@0x1018) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1018) PatchPoint MethodRedefined(Object@0x1018, lambda@0x1020, cme:0x1028) v114:HeapObject[class_exact*:Object@VALUE(0x1018)] = GuardType v57, HeapObject[class_exact*:Object@VALUE(0x1018)] v115:BasicObject = CCallWithFrame v114, :Kernel#lambda@0x1050, block=0x1058 diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index aa71daa0e80567..5e423e37c542b7 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -119,7 +119,7 @@ mod snapshot_tests { v13:Fixnum[1] = Const Value(1) v15:Fixnum[2] = Const Value(2) v16:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v11, v13, v15], locals: [] } - PatchPoint NoSingletonClass(Object@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1010) PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) v25:HeapObject[class_exact*:Object@VALUE(0x1010)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1010)] v26:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v13, v15, v11], locals: [] } @@ -157,7 +157,7 @@ mod snapshot_tests { v11:Fixnum[1] = Const Value(1) v13:Fixnum[2] = Const Value(2) v14:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v11, v13], locals: [] } - PatchPoint NoSingletonClass(Object@0x1010) + PatchPoint NoSingletonClassWithShadowingMethod(Object@0x1010) PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020) v23:HeapObject[class_exact*:Object@VALUE(0x1010)] = GuardType v6, HeapObject[class_exact*:Object@VALUE(0x1010)] v24:BasicObject = SendDirect v23, 0x1048, :foo (0x1058), v11, v13 diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index 7aa13cbfcb4bdd..a5dd893d978648 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -93,9 +93,9 @@ pub struct Invariants { /// Set of patch points that assume that the interpreter is running with only one ractor single_ractor_patch_points: HashSet, - /// Map from a class to a set of patch points that assume objects of the class - /// will have no singleton class. - no_singleton_class_patch_points: HashMap>, + /// Map from a class to a set of patch points that assume no singleton class of an + /// instance of the class has a method that shadows a method on the class. + no_singleton_class_shadowing_patch_points: HashMap>, } impl Invariants { @@ -104,7 +104,7 @@ impl Invariants { self.update_ep_escape_iseqs(); self.update_no_ep_escape_iseq_patch_points(); self.update_cme_patch_points(); - self.update_no_singleton_class_patch_points(); + self.update_no_singleton_class_shadowing_patch_points(); } /// Forget an ISEQ when freeing it. We need to because a) if the address is reused, we'd be @@ -125,7 +125,7 @@ impl Invariants { /// Forget a class when freeing it. See [Self::forget_iseq] for reasoning. pub fn forget_klass(&mut self, klass: VALUE) { - self.no_singleton_class_patch_points.remove(&klass); + self.no_singleton_class_shadowing_patch_points.remove(&klass); } /// Update ISEQ references in Invariants::ep_escape_iseqs @@ -160,15 +160,15 @@ impl Invariants { self.cme_patch_points = updated_cme_patch_points; } - fn update_no_singleton_class_patch_points(&mut self) { - let updated_no_singleton_class_patch_points = std::mem::take(&mut self.no_singleton_class_patch_points) + fn update_no_singleton_class_shadowing_patch_points(&mut self) { + let updated_no_singleton_class_shadowing_patch_points = std::mem::take(&mut self.no_singleton_class_shadowing_patch_points) .into_iter() .map(|(klass, patch_points)| { let new_klass = unsafe { rb_gc_location(klass) }; (new_klass, patch_points) }) .collect(); - self.no_singleton_class_patch_points = updated_no_singleton_class_patch_points; + self.no_singleton_class_shadowing_patch_points = updated_no_singleton_class_shadowing_patch_points; } } @@ -301,15 +301,16 @@ pub fn track_stable_constant_names_assumption( } } -/// Track a patch point for objects of a given class will have no singleton class. -pub fn track_no_singleton_class_assumption( +/// Track a patch point assuming no singleton class of an instance of a given class +/// has a method that shadows a method on the class. +pub fn track_no_singleton_class_shadowing_assumption( klass: VALUE, patch_point_ptr: CodePtr, side_exit_ptr: CodePtr, version: IseqVersionRef, ) { let invariants = ZJITState::get_invariants(); - invariants.no_singleton_class_patch_points.entry(klass).or_default().insert(PatchPoint::new( + invariants.no_singleton_class_shadowing_patch_points.entry(klass).or_default().insert(PatchPoint::new( patch_point_ptr, side_exit_ptr, version, @@ -439,39 +440,39 @@ pub extern "C" fn rb_zjit_tracing_invalidate_all() { }); } -/// Returns true if we've seen a singleton class of a given class since boot. -/// This is used to avoid an invalidation loop where we repeatedly compile code -/// that assumes no singleton class, only to have it invalidated. -pub fn has_singleton_class_of(klass: VALUE) -> bool { +/// Returns true if we've seen a singleton class with a shadowing method for a given class +/// since boot. This is used to avoid an invalidation loop where we repeatedly compile code +/// that assumes no shadowing, only to have it invalidated. +pub fn has_singleton_class_method_shadowing(klass: VALUE) -> bool { ZJITState::get_invariants() - .no_singleton_class_patch_points + .no_singleton_class_shadowing_patch_points .get(&klass) .map_or(false, |patch_points| patch_points.is_empty()) } #[unsafe(no_mangle)] -pub extern "C" fn rb_zjit_invalidate_no_singleton_class(klass: VALUE) { +pub extern "C" fn rb_zjit_invalidate_singleton_class_has_shadowing_method(klass: VALUE) { if !zjit_enabled_p() { return; } with_vm_lock(src_loc!(), || { let invariants = ZJITState::get_invariants(); - match invariants.no_singleton_class_patch_points.get_mut(&klass) { + match invariants.no_singleton_class_shadowing_patch_points.get_mut(&klass) { Some(patch_points) => { - // Invalidate existing patch points and let has_singleton_class_of() + // Invalidate existing patch points and let has_singleton_class_method_shadowing() // return true when they are compiled again let patch_points = mem::take(patch_points); if !patch_points.is_empty() { let cb = ZJITState::get_code_block(); - debug!("Singleton class created for {:?}", klass); - compile_patch_points!(cb, patch_points, "Singleton class created for {:?}", klass); + debug!("Singleton class with shadowing method for {:?}", klass); + compile_patch_points!(cb, patch_points, "Singleton class with shadowing method for {:?}", klass); cb.mark_all_executable(); } } None => { - // Let has_singleton_class_of() return true for this class - invariants.no_singleton_class_patch_points.insert(klass, HashSet::new()); + // Let has_singleton_class_method_shadowing() return true for this class + invariants.no_singleton_class_shadowing_patch_points.insert(klass, HashSet::new()); } } }); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 059964b8e8dd07..7ffe8930d680bc 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -215,7 +215,7 @@ make_counters! { exit_patchpoint_no_tracepoint, exit_patchpoint_no_ep_escape, exit_patchpoint_single_ractor_mode, - exit_patchpoint_no_singleton_class, + exit_patchpoint_no_singleton_class_shadowing, exit_callee_side_exit, exit_obj_to_string_fallback, exit_interrupt, @@ -259,7 +259,7 @@ make_counters! { // Caller has keyword arguments but callee doesn't expect them. send_fallback_unexpected_keyword_args, // Singleton class previously created for receiver class. - send_fallback_singleton_class_seen, + send_fallback_singleton_class_shadowing_seen, send_fallback_bmethod_non_iseq_proc, send_fallback_obj_to_string_not_string, send_fallback_send_cfunc_variadic, @@ -597,8 +597,8 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { => exit_patchpoint_no_ep_escape, PatchPoint(Invariant::SingleRactorMode) => exit_patchpoint_single_ractor_mode, - PatchPoint(Invariant::NoSingletonClass { .. }) - => exit_patchpoint_no_singleton_class, + PatchPoint(Invariant::NoSingletonClassWithShadowingMethod { .. }) + => exit_patchpoint_no_singleton_class_shadowing, } } @@ -636,7 +636,7 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter SendCfuncArrayVariadic => send_fallback_send_cfunc_array_variadic, ComplexArgPass => send_fallback_one_or_more_complex_arg_pass, UnexpectedKeywordArgs => send_fallback_unexpected_keyword_args, - SingletonClassSeen => send_fallback_singleton_class_seen, + SingletonClassWithShadowingMethodSeen => send_fallback_singleton_class_shadowing_seen, ArgcParamMismatch => send_fallback_argc_param_mismatch, BmethodNonIseqProc => send_fallback_bmethod_non_iseq_proc, SendNotOptimizedMethodType(_) => send_fallback_send_not_optimized_method_type,