@@ -648,6 +648,11 @@ pub enum Insn {
648648 GetSpecialSymbol { symbol_type: SpecialBackrefSymbol, state: InsnId },
649649 GetSpecialNumber { nth: u64, state: InsnId },
650650
651+ /// Get a class variable `id`
652+ GetClassVar { id: ID, ic: *const iseq_inline_cvar_cache_entry, state: InsnId },
653+ /// Set a class variable `id` to `val`
654+ SetClassVar { id: ID, val: InsnId, ic: *const iseq_inline_cvar_cache_entry, state: InsnId },
655+
651656 /// Own a FrameState so that instructions can look up their dominating FrameState when
652657 /// generating deopt side-exits and frame reconstruction metadata. Does not directly generate
653658 /// any code.
@@ -811,7 +816,7 @@ impl Insn {
811816 match self {
812817 Insn::Jump(_)
813818 | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::EntryPoint { .. } | Insn::Return { .. }
814- | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
819+ | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn:: ArrayExtend { .. }
815820 | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. }
816821 | Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::IncrCounterPtr { .. }
817822 | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false,
@@ -1130,6 +1135,8 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
11301135 Insn::SetLocal { val, level, ep_offset } => write!(f, "SetLocal l{level}, EP@{ep_offset}, {val}"),
11311136 Insn::GetSpecialSymbol { symbol_type, .. } => write!(f, "GetSpecialSymbol {symbol_type:?}"),
11321137 Insn::GetSpecialNumber { nth, .. } => write!(f, "GetSpecialNumber {nth}"),
1138+ Insn::GetClassVar { id, .. } => write!(f, "GetClassVar :{}", id.contents_lossy()),
1139+ Insn::SetClassVar { id, val, .. } => write!(f, "SetClassVar :{}, {val}", id.contents_lossy()),
11331140 Insn::ToArray { val, .. } => write!(f, "ToArray {val}"),
11341141 Insn::ToNewArray { val, .. } => write!(f, "ToNewArray {val}"),
11351142 Insn::ArrayExtend { left, right, .. } => write!(f, "ArrayExtend {left}, {right}"),
@@ -1716,6 +1723,8 @@ impl Function {
17161723 &LoadIvarEmbedded { self_val, id, index } => LoadIvarEmbedded { self_val: find!(self_val), id, index },
17171724 &LoadIvarExtended { self_val, id, index } => LoadIvarExtended { self_val: find!(self_val), id, index },
17181725 &SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val: find!(val), state },
1726+ &GetClassVar { id, ic, state } => GetClassVar { id, ic, state },
1727+ &SetClassVar { id, val, ic, state } => SetClassVar { id, val: find!(val), ic, state },
17191728 &SetLocal { val, ep_offset, level } => SetLocal { val: find!(val), ep_offset, level },
17201729 &GetSpecialSymbol { symbol_type, state } => GetSpecialSymbol { symbol_type, state },
17211730 &GetSpecialNumber { nth, state } => GetSpecialNumber { nth, state },
@@ -1765,7 +1774,7 @@ impl Function {
17651774 Insn::Param { .. } => unimplemented!("params should not be present in block.insns"),
17661775 Insn::SetGlobal { .. } | Insn::Jump(_) | Insn::EntryPoint { .. }
17671776 | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. }
1768- | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
1777+ | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn:: ArrayExtend { .. }
17691778 | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_)
17701779 | Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::IncrCounterPtr { .. } =>
17711780 panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]),
@@ -1848,6 +1857,7 @@ impl Function {
18481857 Insn::LoadIvarExtended { .. } => types::BasicObject,
18491858 Insn::GetSpecialSymbol { .. } => types::BasicObject,
18501859 Insn::GetSpecialNumber { .. } => types::BasicObject,
1860+ Insn::GetClassVar { .. } => types::BasicObject,
18511861 Insn::ToNewArray { .. } => types::ArrayExact,
18521862 Insn::ToArray { .. } => types::ArrayExact,
18531863 Insn::ObjToString { .. } => types::BasicObject,
@@ -3156,6 +3166,13 @@ impl Function {
31563166 worklist.push_back(val);
31573167 worklist.push_back(state);
31583168 }
3169+ &Insn::GetClassVar { state, .. } => {
3170+ worklist.push_back(state);
3171+ }
3172+ &Insn::SetClassVar { val, state, .. } => {
3173+ worklist.push_back(val);
3174+ worklist.push_back(state);
3175+ }
31593176 &Insn::ArrayPush { array, val, state } => {
31603177 worklist.push_back(array);
31613178 worklist.push_back(val);
@@ -4639,6 +4656,20 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
46394656 let val = state.stack_pop()?;
46404657 fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, val, state: exit_id });
46414658 }
4659+ YARVINSN_getclassvariable => {
4660+ let id = ID(get_arg(pc, 0).as_u64());
4661+ let ic = get_arg(pc, 1).as_ptr();
4662+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
4663+ let result = fun.push_insn(block, Insn::GetClassVar { id, ic, state: exit_id });
4664+ state.stack_push(result);
4665+ }
4666+ YARVINSN_setclassvariable => {
4667+ let id = ID(get_arg(pc, 0).as_u64());
4668+ let ic = get_arg(pc, 1).as_ptr();
4669+ let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
4670+ let val = state.stack_pop()?;
4671+ fun.push_insn(block, Insn::SetClassVar { id, val, ic, state: exit_id });
4672+ }
46424673 YARVINSN_opt_reverse => {
46434674 // Reverse the order of the top N stack items.
46444675 let n = get_arg(pc, 0).as_usize();
@@ -7429,6 +7460,59 @@ mod tests {
74297460 assert_eq!(VALUE::fixnum_from_usize(1), result);
74307461 }
74317462
7463+ #[test]
7464+ fn test_getclassvariable() {
7465+ eval("
7466+ class Foo
7467+ def self.test = @@foo
7468+ end
7469+ ");
7470+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test"));
7471+ assert!(iseq_contains_opcode(iseq, YARVINSN_getclassvariable), "iseq Foo.test does not contain getclassvariable");
7472+ let function = iseq_to_hir(iseq).unwrap();
7473+ assert_snapshot!(hir_string_function(&function), @r"
7474+ fn test@<compiled>:3:
7475+ bb0():
7476+ EntryPoint interpreter
7477+ v1:BasicObject = LoadSelf
7478+ Jump bb2(v1)
7479+ bb1(v4:BasicObject):
7480+ EntryPoint JIT(0)
7481+ Jump bb2(v4)
7482+ bb2(v6:BasicObject):
7483+ v11:BasicObject = GetClassVar :@@foo
7484+ CheckInterrupts
7485+ Return v11
7486+ ");
7487+ }
7488+
7489+ #[test]
7490+ fn test_setclassvariable() {
7491+ eval("
7492+ class Foo
7493+ def self.test = @@foo = 42
7494+ end
7495+ ");
7496+ let iseq = crate::cruby::with_rubyvm(|| get_method_iseq("Foo", "test"));
7497+ assert!(iseq_contains_opcode(iseq, YARVINSN_setclassvariable), "iseq Foo.test does not contain setclassvariable");
7498+ let function = iseq_to_hir(iseq).unwrap();
7499+ assert_snapshot!(hir_string_function(&function), @r"
7500+ fn test@<compiled>:3:
7501+ bb0():
7502+ EntryPoint interpreter
7503+ v1:BasicObject = LoadSelf
7504+ Jump bb2(v1)
7505+ bb1(v4:BasicObject):
7506+ EntryPoint JIT(0)
7507+ Jump bb2(v4)
7508+ bb2(v6:BasicObject):
7509+ v10:Fixnum[42] = Const Value(42)
7510+ SetClassVar :@@foo, v10
7511+ CheckInterrupts
7512+ Return v10
7513+ ");
7514+ }
7515+
74327516 #[test]
74337517 fn test_setglobal() {
74347518 eval("
0 commit comments