This commit is contained in:
Donghee Na 2025-08-15 11:07:28 +09:00 committed by GitHub
commit 07f7ba7c30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 168 additions and 3 deletions

View file

@ -509,6 +509,116 @@ class TestZJIT < Test::Unit::TestCase
}, insns: [:opt_ge], call_threshold: 2
end
def test_new_hash_empty
assert_compiles '{}', %q{
def test = {}
test
}, insns: [:newhash]
end
def test_new_hash_nonempty
assert_compiles '{"key" => "value", 42 => 100}', %q{
def test
key = "key"
value = "value"
num = 42
result = 100
{key => value, num => result}
end
test
}, insns: [:newhash]
end
def test_new_hash_single_key_value
assert_compiles '{"key" => "value"}', %q{
def test = {"key" => "value"}
test
}, insns: [:newhash]
end
def test_new_hash_with_computation
assert_compiles '{"sum" => 5, "product" => 6}', %q{
def test(a, b)
{"sum" => a + b, "product" => a * b}
end
test(2, 3)
}, insns: [:newhash]
end
def test_new_hash_with_user_defined_hash_method
assert_runs 'true', %q{
class CustomKey
attr_reader :val
def initialize(val)
@val = val
end
def hash
@val.hash
end
def eql?(other)
other.is_a?(CustomKey) && @val == other.val
end
end
def test
key = CustomKey.new("key")
hash = {key => "value"}
hash[key] == "value"
end
test
}
end
def test_new_hash_with_user_hash_method_exception
assert_runs 'RuntimeError', %q{
class BadKey
def hash
raise "Hash method failed!"
end
end
def test
key = BadKey.new
{key => "value"}
end
begin
test
rescue => e
e.class
end
}
end
def test_new_hash_with_user_eql_method_exception
assert_runs 'RuntimeError', %q{
class BadKey
def hash
42
end
def eql?(other)
raise "Eql method failed!"
end
end
def test
key1 = BadKey.new
key2 = BadKey.new
{key1 => "value1", key2 => "value2"}
end
begin
test
rescue => e
e.class
end
}
end
def test_opt_hash_freeze
assert_compiles '{}', <<~RUBY, insns: [:opt_hash_freeze]
def test = {}.freeze

View file

@ -1,6 +1,6 @@
use std::cell::Cell;
use std::rc::Rc;
use std::ffi::{c_int, c_void};
use std::ffi::{c_int, c_long, c_void};
use crate::asm::Label;
use crate::backend::current::{Reg, ALLOC_REGS};
@ -333,6 +333,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
let out_opnd = match insn {
Insn::Const { val: Const::Value(val) } => gen_const(*val),
Insn::NewArray { elements, state } => gen_new_array(asm, opnds!(elements), &function.frame_state(*state)),
Insn::NewHash { elements, state } => gen_new_hash(jit, asm, elements, &function.frame_state(*state))?,
Insn::NewRange { low, high, flag, state } => gen_new_range(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)),
Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)),
Insn::StringCopy { val, chilled, state } => gen_string_copy(asm, opnd!(val), *chilled, &function.frame_state(*state)),
@ -387,7 +388,6 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
| Insn::FixnumDiv { .. }
| Insn::FixnumMod { .. }
| Insn::HashDup { .. }
| Insn::NewHash { .. }
| Insn::Send { .. }
| Insn::Throw { .. }
| Insn::ToArray { .. }
@ -944,7 +944,7 @@ fn gen_new_array(
) -> lir::Opnd {
gen_prepare_call_with_gc(asm, state);
let length: ::std::os::raw::c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
let length: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
let new_array = asm_ccall!(asm, rb_ary_new_capa, length.into());
@ -955,6 +955,61 @@ fn gen_new_array(
new_array
}
/// Compile a new hash instruction
fn gen_new_hash(
jit: &mut JITState,
asm: &mut Assembler,
elements: &Vec<(InsnId, InsnId)>,
state: &FrameState,
) -> Option<lir::Opnd> {
gen_prepare_non_leaf_call(jit, asm, state)?;
let cap: c_long = elements.len().try_into().expect("Unable to fit length of elements into c_long");
let new_hash = asm_ccall!(asm, rb_hash_new_with_size, lir::Opnd::Imm(cap));
if !elements.is_empty() {
let mut pairs = Vec::new();
for (key_id, val_id) in elements.iter() {
let key = jit.get_opnd(*key_id)?;
let val = jit.get_opnd(*val_id)?;
pairs.push(key);
pairs.push(val);
}
let n = pairs.len();
// Calculate the compile-time NATIVE_STACK_PTR offset from NATIVE_BASE_PTR
// At this point, frame_setup(&[], jit.c_stack_slots) has been called,
// which allocated aligned_stack_bytes(jit.c_stack_slots) on the stack
let frame_size = aligned_stack_bytes(jit.c_stack_slots);
let allocation_size = aligned_stack_bytes(n);
asm_comment!(asm, "allocate {} bytes on C stack for {} hash elements", allocation_size, n);
asm.sub_into(NATIVE_STACK_PTR, allocation_size.into());
// Calculate the total offset from NATIVE_BASE_PTR to our buffer
let total_offset_from_base = (frame_size + allocation_size) as i32;
for (idx, &pair_opnd) in pairs.iter().enumerate() {
let slot_offset = -total_offset_from_base + (idx as i32 * SIZEOF_VALUE_I32);
asm.mov(
Opnd::mem(VALUE_BITS, NATIVE_BASE_PTR, slot_offset),
pair_opnd
);
}
let argv = asm.lea(Opnd::mem(64, NATIVE_BASE_PTR, -total_offset_from_base));
let argc = (elements.len() * 2) as ::std::os::raw::c_long;
asm_ccall!(asm, rb_hash_bulk_insert, lir::Opnd::Imm(argc), argv, new_hash);
asm_comment!(asm, "restore C stack pointer");
asm.add_into(NATIVE_STACK_PTR, allocation_size.into());
}
Some(new_hash)
}
/// Compile a new range instruction
fn gen_new_range(
asm: &mut Assembler,