Add codegen support for ArrayDup

This commit is contained in:
Aiden Fox Ivey 2025-04-07 16:19:28 -04:00 committed by Takashi Kokubun
parent 0e766c9014
commit 164bd8e1a6
Notes: git 2025-04-18 13:47:39 +00:00
3 changed files with 48 additions and 18 deletions

View File

@ -187,6 +187,13 @@ class TestZJIT < Test::Unit::TestCase
}, call_threshold: 2 }, call_threshold: 2
end end
def test_array_dup
assert_compiles '[1, 2, 3]', %q{
def test = [1,2,3]
test
}
end
def test_if def test_if
assert_compiles '[0, nil]', %q{ assert_compiles '[0, nil]', %q{
def test(n) def test(n)

View File

@ -182,6 +182,7 @@ fn gen_insn(jit: &mut JITState, asm: &mut Assembler, function: &Function, insn_i
let out_opnd = match insn { let out_opnd = match insn {
Insn::PutSelf => gen_putself(), Insn::PutSelf => gen_putself(),
Insn::Const { val: Const::Value(val) } => gen_const(*val), Insn::Const { val: Const::Value(val) } => gen_const(*val),
Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)),
Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"), Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"),
Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment
Insn::Jump(branch) => return gen_jump(jit, asm, branch), Insn::Jump(branch) => return gen_jump(jit, asm, branch),
@ -396,6 +397,23 @@ fn gen_send_without_block(
Some(ret) Some(ret)
} }
/// Compile an array duplication instruction
fn gen_array_dup(
asm: &mut Assembler,
val: lir::Opnd,
state: &FrameState,
) -> lir::Opnd {
asm_comment!(asm, "call rb_ary_resurrect");
// Save PC
gen_save_pc(asm, state);
asm.ccall(
rb_ary_resurrect as *const u8,
vec![val],
)
}
/// Compile code that exits from JIT code with a return value /// Compile code that exits from JIT code with a return value
fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> { fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> {
// Pop the current frame (ec->cfp++) // Pop the current frame (ec->cfp++)

View File

@ -300,7 +300,7 @@ pub enum Insn {
NewArray { elements: Vec<InsnId> }, NewArray { elements: Vec<InsnId> },
ArraySet { array: InsnId, idx: usize, val: InsnId }, ArraySet { array: InsnId, idx: usize, val: InsnId },
ArrayDup { val: InsnId }, ArrayDup { val: InsnId, state: InsnId },
// Check if the value is truthy and "return" a C boolean. In reality, we will likely fuse this // Check if the value is truthy and "return" a C boolean. In reality, we will likely fuse this
// with IfTrue/IfFalse in the backend to generate jcc. // with IfTrue/IfFalse in the backend to generate jcc.
@ -433,7 +433,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
Ok(()) Ok(())
} }
Insn::ArraySet { array, idx, val } => { write!(f, "ArraySet {array}, {idx}, {val}") } Insn::ArraySet { array, idx, val } => { write!(f, "ArraySet {array}, {idx}, {val}") }
Insn::ArrayDup { val } => { write!(f, "ArrayDup {val}") } Insn::ArrayDup { val, .. } => { write!(f, "ArrayDup {val}") }
Insn::StringCopy { val } => { write!(f, "StringCopy {val}") } Insn::StringCopy { val } => { write!(f, "StringCopy {val}") }
Insn::Test { val } => { write!(f, "Test {val}") } Insn::Test { val } => { write!(f, "Test {val}") }
Insn::Jump(target) => { write!(f, "Jump {target}") } Insn::Jump(target) => { write!(f, "Jump {target}") }
@ -789,7 +789,7 @@ impl Function {
state: *state, state: *state,
}, },
ArraySet { array, idx, val } => ArraySet { array: find!(*array), idx: *idx, val: find!(*val) }, ArraySet { array, idx, val } => ArraySet { array: find!(*array), idx: *idx, val: find!(*val) },
ArrayDup { val } => ArrayDup { val: find!(*val) }, ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state },
CCall { cfun, args, name, return_type } => CCall { cfun: *cfun, args: args.iter().map(|arg| find!(*arg)).collect(), name: *name, return_type: *return_type }, CCall { cfun, args, name, return_type } => CCall { cfun: *cfun, args: args.iter().map(|arg| find!(*arg)).collect(), name: *name, return_type: *return_type },
Defined { .. } => todo!("find(Defined)"), Defined { .. } => todo!("find(Defined)"),
} }
@ -1196,7 +1196,6 @@ impl Function {
| Insn::GetConstantPath { .. } => | Insn::GetConstantPath { .. } =>
{} {}
Insn::StringCopy { val } Insn::StringCopy { val }
| Insn::ArrayDup { val }
| Insn::StringIntern { val } | Insn::StringIntern { val }
| Insn::Return { val } | Insn::Return { val }
| Insn::Defined { v: val, .. } | Insn::Defined { v: val, .. }
@ -1240,6 +1239,10 @@ impl Function {
worklist.push_back(val); worklist.push_back(val);
worklist.extend(args); worklist.extend(args);
} }
Insn::ArrayDup { val , state } => {
worklist.push_back(val);
worklist.push_back(state);
}
Insn::Send { self_val, args, state, .. } Insn::Send { self_val, args, state, .. }
| Insn::SendWithoutBlock { self_val, args, state, .. } | Insn::SendWithoutBlock { self_val, args, state, .. }
| Insn::SendWithoutBlockDirect { self_val, args, state, .. } => { | Insn::SendWithoutBlockDirect { self_val, args, state, .. } => {
@ -1639,7 +1642,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
} }
YARVINSN_duparray => { YARVINSN_duparray => {
let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) }); let val = fun.push_insn(block, Insn::Const { val: Const::Value(get_arg(pc, 0)) });
let insn_id = fun.push_insn(block, Insn::ArrayDup { val }); let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state.clone() });
let insn_id = fun.push_insn(block, Insn::ArrayDup { val, state: exit_id });
state.stack_push(insn_id); state.stack_push(insn_id);
} }
YARVINSN_putobject_INT2FIX_0_ => { YARVINSN_putobject_INT2FIX_0_ => {
@ -2050,7 +2054,8 @@ mod infer_tests {
fn arraydup() { fn arraydup() {
let mut function = Function::new(std::ptr::null()); let mut function = Function::new(std::ptr::null());
let arr = function.push_insn(function.entry_block, Insn::NewArray { elements: vec![] }); let arr = function.push_insn(function.entry_block, Insn::NewArray { elements: vec![] });
let val = function.push_insn(function.entry_block, Insn::ArrayDup { val: arr }); // Fake FrameState index of 0usize
let val = function.push_insn(function.entry_block, Insn::ArrayDup { val: arr, state: InsnId(0usize) });
assert_bit_equal(function.infer_type(val), types::ArrayExact); assert_bit_equal(function.infer_type(val), types::ArrayExact);
} }
@ -2197,8 +2202,8 @@ mod tests {
fn test: fn test:
bb0(): bb0():
v1:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v1:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v2:ArrayExact = ArrayDup v1 v3:ArrayExact = ArrayDup v1
Return v2 Return v3
"#]]); "#]]);
} }
@ -2657,16 +2662,16 @@ mod tests {
bb0(): bb0():
v1:BasicObject = PutSelf v1:BasicObject = PutSelf
v2:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v2:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
v3:ArrayExact = ArrayDup v2 v4:ArrayExact = ArrayDup v2
v4:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v5:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
v5:ArrayExact = ArrayDup v4 v7:ArrayExact = ArrayDup v5
v6:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010))
v7:StringExact = StringCopy v6
v8:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010)) v8:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010))
v9:StringExact = StringCopy v8 v9:StringExact = StringCopy v8
v11:BasicObject = SendWithoutBlock v1, :unknown_method, v3, v5, v7, v9 v10:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010))
PatchPoint CalleeModifiedLocals(v11) v11:StringExact = StringCopy v10
Return v11 v13:BasicObject = SendWithoutBlock v1, :unknown_method, v4, v7, v9, v11
PatchPoint CalleeModifiedLocals(v13)
Return v13
"#]]); "#]]);
} }
} }
@ -3018,8 +3023,8 @@ mod opt_tests {
assert_optimized_method_hir("test", expect![[r#" assert_optimized_method_hir("test", expect![[r#"
fn test: fn test:
bb0(): bb0():
v4:Fixnum[5] = Const Value(5) v5:Fixnum[5] = Const Value(5)
Return v4 Return v5
"#]]); "#]]);
} }