@@ -628,6 +628,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
628628 Insn :: ArrayDup { val, state } => gen_array_dup ( jit, asm, function, * val, opnd ! ( val) , & function. frame_state ( * state) ) ,
629629 Insn :: AdjustBounds { index, length } => gen_adjust_bounds ( asm, opnd ! ( index) , opnd ! ( length) ) ,
630630 Insn :: ArrayAref { array, index, .. } => gen_array_aref ( asm, opnd ! ( array) , opnd ! ( index) ) ,
631+ Insn :: ArrayArefOrNil { array, index } => gen_array_aref_or_nil ( jit, asm, opnd ! ( array) , opnd ! ( index) ) ,
631632 Insn :: ArrayAset { array, index, val } => {
632633 no_output ! ( gen_array_aset( asm, opnd!( array) , opnd!( index) , opnd!( val) ) )
633634 }
@@ -638,6 +639,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
638639 Insn :: StringCopy { val, chilled, state } => gen_string_copy ( jit, asm, function, * val, opnd ! ( val) , * chilled, & function. frame_state ( * state) ) ,
639640 Insn :: StringConcat { strings, state } => gen_string_concat ( jit, asm, function, opnds ! ( strings) , & function. frame_state ( * state) ) ,
640641 & Insn :: StringGetbyte { string, index } => gen_string_getbyte ( asm, opnd ! ( string) , opnd ! ( index) ) ,
642+ & Insn :: StringGetbyteOrNil { string, index } => gen_string_getbyte_or_nil ( jit, asm, opnd ! ( string) , opnd ! ( index) ) ,
641643 Insn :: StringSetbyteFixnum { string, index, value } => gen_string_setbyte_fixnum ( asm, opnd ! ( string) , opnd ! ( index) , opnd ! ( value) ) ,
642644 Insn :: StringAppend { recv, other, state } => gen_string_append ( jit, asm, function, opnd ! ( recv) , opnd ! ( other) , & function. frame_state ( * state) ) ,
643645 Insn :: StringAppendCodepoint { recv, other, state } => gen_string_append_codepoint ( jit, asm, function, opnd ! ( recv) , opnd ! ( other) , & function. frame_state ( * state) ) ,
@@ -2217,6 +2219,69 @@ fn gen_array_aref(
22172219 asm. load ( Opnd :: mem ( VALUE_BITS , elem_ptr, 0 ) )
22182220}
22192221
2222+ /// Compile array access (`array[index]`) where `index` is a raw (unboxed)
2223+ /// C long that may be negative or out of bounds. Out-of-bounds reads yield
2224+ /// nil instead of side-exiting, matching rb_ary_entry semantics.
2225+ fn gen_array_aref_or_nil (
2226+ jit : & mut JITState ,
2227+ asm : & mut Assembler ,
2228+ array : Opnd ,
2229+ index : Opnd ,
2230+ ) -> lir:: Opnd {
2231+ asm_comment ! ( asm, "ArrayArefOrNil" ) ;
2232+ let hir_block_id = asm. current_block ( ) . hir_block_id ;
2233+ let rpo_idx = asm. current_block ( ) . rpo_index ;
2234+
2235+ let load_block = asm. new_block ( hir_block_id, false , rpo_idx) ;
2236+ let result_block = asm. new_block ( hir_block_id, false , rpo_idx) ;
2237+ let result_edge = |v| Target :: Block ( Box :: new ( lir:: BranchEdge {
2238+ target : result_block,
2239+ args : vec ! [ v] ,
2240+ } ) ) ;
2241+
2242+ let idx = asm. load_mem ( index) ;
2243+ let array = asm. load_mem ( array) ;
2244+ let length = gen_array_length ( asm, array) ;
2245+ let array_ptr = gen_array_ptr ( asm, array) ;
2246+
2247+ // Adjust a negative index by the length, as in gen_adjust_bounds
2248+ let adjusted = asm. add ( idx, length) ;
2249+ asm. test ( idx, idx) ;
2250+ let adjusted = asm. csel_l ( adjusted, idx) ;
2251+
2252+ // In bounds iff (u64)adjusted < (u64)length; this single unsigned
2253+ // comparison covers both negative-out-of-range and past-the-end indices.
2254+ asm. cmp ( adjusted, length) ;
2255+ asm. jb ( Target :: Block ( Box :: new ( lir:: BranchEdge {
2256+ target : load_block,
2257+ args : vec ! [ array_ptr, adjusted] ,
2258+ } ) ) ) ;
2259+
2260+ // Out of bounds: nil
2261+ asm. jmp ( result_edge ( Qnil . into ( ) ) ) ;
2262+
2263+ // In bounds: load the element
2264+ asm. set_current_block ( load_block) ;
2265+ let label = jit. get_label ( asm, load_block, hir_block_id) ;
2266+ asm. write_label ( label) ;
2267+ let array_ptr = asm. new_block_param ( VALUE_BITS ) ;
2268+ asm. current_block ( ) . add_parameter ( array_ptr) ;
2269+ let adjusted = asm. new_block_param ( VALUE_BITS ) ;
2270+ asm. current_block ( ) . add_parameter ( adjusted) ;
2271+ let elem_offset = asm. lshift ( adjusted, Opnd :: UImm ( SIZEOF_VALUE . trailing_zeros ( ) as u64 ) ) ;
2272+ let elem_ptr = asm. add ( array_ptr, elem_offset) ;
2273+ let elem = asm. load ( Opnd :: mem ( VALUE_BITS , elem_ptr, 0 ) ) ;
2274+ asm. jmp ( result_edge ( elem) ) ;
2275+
2276+ // Join block
2277+ asm. set_current_block ( result_block) ;
2278+ let label = jit. get_label ( asm, result_block, hir_block_id) ;
2279+ asm. write_label ( label) ;
2280+ let param = asm. new_block_param ( VALUE_BITS ) ;
2281+ asm. current_block ( ) . add_parameter ( param) ;
2282+ param
2283+ }
2284+
22202285fn gen_array_aset (
22212286 asm : & mut Assembler ,
22222287 array : Opnd ,
@@ -4068,6 +4133,77 @@ fn gen_string_getbyte(asm: &mut Assembler, string: Opnd, index: Opnd) -> Opnd {
40684133 // TODO(max): Use SIB indexing here once the backend supports it
40694134 let string_ptr = asm. add ( string_ptr, index) ;
40704135 let byte = asm. load ( Opnd :: mem ( 8 , string_ptr, 0 ) ) ;
4136+ tag_byte ( asm, byte)
4137+ }
4138+
4139+ /// Compile byte access (`string.getbyte(index)`) where `index` is a raw
4140+ /// (unboxed) C long that may be negative or out of bounds. Out-of-bounds
4141+ /// reads yield nil instead of side-exiting, matching rb_str_getbyte semantics.
4142+ fn gen_string_getbyte_or_nil (
4143+ jit : & mut JITState ,
4144+ asm : & mut Assembler ,
4145+ string : Opnd ,
4146+ index : Opnd ,
4147+ ) -> lir:: Opnd {
4148+ asm_comment ! ( asm, "StringGetbyteOrNil" ) ;
4149+ let hir_block_id = asm. current_block ( ) . hir_block_id ;
4150+ let rpo_idx = asm. current_block ( ) . rpo_index ;
4151+
4152+ let load_block = asm. new_block ( hir_block_id, false , rpo_idx) ;
4153+ let result_block = asm. new_block ( hir_block_id, false , rpo_idx) ;
4154+ let result_edge = |v| Target :: Block ( Box :: new ( lir:: BranchEdge {
4155+ target : result_block,
4156+ args : vec ! [ v] ,
4157+ } ) ) ;
4158+
4159+ let idx = asm. load_mem ( index) ;
4160+ let string_reg = asm. load_mem ( string) ;
4161+ // struct RString stores `len` at a fixed offset for both embedded and
4162+ // heap strings, so this is a plain load (unlike RArray).
4163+ let length = asm. load ( Opnd :: mem ( VALUE_BITS , string_reg, RUBY_OFFSET_RSTRING_LEN ) ) ;
4164+ let string_ptr = get_string_ptr ( asm, string) ;
4165+
4166+ // Adjust a negative index by the length, as in gen_adjust_bounds
4167+ let adjusted = asm. add ( idx, length) ;
4168+ asm. test ( idx, idx) ;
4169+ let adjusted = asm. csel_l ( adjusted, idx) ;
4170+
4171+ // In bounds iff (u64)adjusted < (u64)length; this single unsigned
4172+ // comparison covers both negative-out-of-range and past-the-end indices.
4173+ asm. cmp ( adjusted, length) ;
4174+ asm. jb ( Target :: Block ( Box :: new ( lir:: BranchEdge {
4175+ target : load_block,
4176+ args : vec ! [ string_ptr, adjusted] ,
4177+ } ) ) ) ;
4178+
4179+ // Out of bounds: nil
4180+ asm. jmp ( result_edge ( Qnil . into ( ) ) ) ;
4181+
4182+ // In bounds: load the byte
4183+ asm. set_current_block ( load_block) ;
4184+ let label = jit. get_label ( asm, load_block, hir_block_id) ;
4185+ asm. write_label ( label) ;
4186+ let string_ptr = asm. new_block_param ( VALUE_BITS ) ;
4187+ asm. current_block ( ) . add_parameter ( string_ptr) ;
4188+ let adjusted = asm. new_block_param ( VALUE_BITS ) ;
4189+ asm. current_block ( ) . add_parameter ( adjusted) ;
4190+ // TODO(max): Use SIB indexing here once the backend supports it
4191+ let byte_ptr = asm. add ( string_ptr, adjusted) ;
4192+ let byte = asm. load ( Opnd :: mem ( 8 , byte_ptr, 0 ) ) ;
4193+ let byte = tag_byte ( asm, byte) ;
4194+ asm. jmp ( result_edge ( byte) ) ;
4195+
4196+ // Join block
4197+ asm. set_current_block ( result_block) ;
4198+ let label = jit. get_label ( asm, result_block, hir_block_id) ;
4199+ asm. write_label ( label) ;
4200+ let param = asm. new_block_param ( VALUE_BITS ) ;
4201+ asm. current_block ( ) . add_parameter ( param) ;
4202+ param
4203+ }
4204+
4205+ /// Zero-extend a byte loaded from a string and tag it as a Fixnum
4206+ fn tag_byte ( asm : & mut Assembler , byte : Opnd ) -> Opnd {
40714207 // Zero-extend the byte to 64 bits
40724208 let byte = byte. with_num_bits ( 64 ) ;
40734209 let byte = asm. and ( byte, 0xFF . into ( ) ) ;
0 commit comments