@@ -445,6 +445,7 @@ pub enum Insn {
445445
446446 StringCopy { val : InsnId , chilled : bool , state : InsnId } ,
447447 StringIntern { val : InsnId } ,
448+ StringConcat { strings : Vec < InsnId > , state : InsnId } ,
448449
449450 /// Put special object (VMCORE, CBASE, etc.) based on value_type
450451 PutSpecialObject { value_type : SpecialObjectType } ,
@@ -675,6 +676,16 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
675676 Insn :: ArrayDup { val, .. } => { write ! ( f, "ArrayDup {val}" ) }
676677 Insn :: HashDup { val, .. } => { write ! ( f, "HashDup {val}" ) }
677678 Insn :: StringCopy { val, .. } => { write ! ( f, "StringCopy {val}" ) }
679+ Insn :: StringConcat { strings, .. } => {
680+ write ! ( f, "StringConcat" ) ?;
681+ let mut prefix = " " ;
682+ for string in strings {
683+ write ! ( f, "{prefix}{string}" ) ?;
684+ prefix = ", " ;
685+ }
686+
687+ Ok ( ( ) )
688+ }
678689 Insn :: Test { val } => { write ! ( f, "Test {val}" ) }
679690 Insn :: IsNil { val } => { write ! ( f, "IsNil {val}" ) }
680691 Insn :: Jump ( target) => { write ! ( f, "Jump {target}" ) }
@@ -1135,6 +1146,7 @@ impl Function {
11351146 & Throw { throw_state, val } => Throw { throw_state, val : find ! ( val) } ,
11361147 & StringCopy { val, chilled, state } => StringCopy { val : find ! ( val) , chilled, state } ,
11371148 & StringIntern { val } => StringIntern { val : find ! ( val) } ,
1149+ & StringConcat { ref strings, state } => StringConcat { strings : find_vec ! ( strings) , state : find ! ( state) } ,
11381150 & Test { val } => Test { val : find ! ( val) } ,
11391151 & IsNil { val } => IsNil { val : find ! ( val) } ,
11401152 & Jump ( ref target) => Jump ( find_branch_edge ! ( target) ) ,
@@ -1258,6 +1270,7 @@ impl Function {
12581270 Insn :: IsNil { .. } => types:: CBool ,
12591271 Insn :: StringCopy { .. } => types:: StringExact ,
12601272 Insn :: StringIntern { .. } => types:: StringExact ,
1273+ Insn :: StringConcat { .. } => types:: StringExact ,
12611274 Insn :: NewArray { .. } => types:: ArrayExact ,
12621275 Insn :: ArrayDup { .. } => types:: ArrayExact ,
12631276 Insn :: NewHash { .. } => types:: HashExact ,
@@ -1887,6 +1900,10 @@ impl Function {
18871900 worklist. push_back ( high) ;
18881901 worklist. push_back ( state) ;
18891902 }
1903+ & Insn :: StringConcat { ref strings, state, .. } => {
1904+ worklist. extend ( strings) ;
1905+ worklist. push_back ( state) ;
1906+ }
18901907 | & Insn :: StringIntern { val }
18911908 | & Insn :: Return { val }
18921909 | & Insn :: Throw { val, .. }
@@ -2469,6 +2486,16 @@ impl FrameState {
24692486 self . stack . pop ( ) . ok_or_else ( || ParseError :: StackUnderflow ( self . clone ( ) ) )
24702487 }
24712488
2489+ fn stack_pop_n ( & mut self , count : usize ) -> Result < Vec < InsnId > , ParseError > {
2490+ // Check if we have enough values on the stack
2491+ let stack_len = self . stack . len ( ) ;
2492+ if stack_len < count {
2493+ return Err ( ParseError :: StackUnderflow ( self . clone ( ) ) ) ;
2494+ }
2495+
2496+ Ok ( self . stack . split_off ( stack_len - count) )
2497+ }
2498+
24722499 /// Get a stack-top operand
24732500 fn stack_top ( & self ) -> Result < InsnId , ParseError > {
24742501 self . stack . last ( ) . ok_or_else ( || ParseError :: StackUnderflow ( self . clone ( ) ) ) . copied ( )
@@ -2789,24 +2816,23 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
27892816 let insn_id = fun. push_insn ( block, Insn :: StringIntern { val } ) ;
27902817 state. stack_push ( insn_id) ;
27912818 }
2819+ YARVINSN_concatstrings => {
2820+ let count = get_arg ( pc, 0 ) . as_u32 ( ) ;
2821+ let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
2822+ let strings = state. stack_pop_n ( count as usize ) ?;
2823+ let insn_id = fun. push_insn ( block, Insn :: StringConcat { strings, state : exit_id } ) ;
2824+ state. stack_push ( insn_id) ;
2825+ }
27922826 YARVINSN_newarray => {
27932827 let count = get_arg ( pc, 0 ) . as_usize ( ) ;
27942828 let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
2795- let mut elements = vec ! [ ] ;
2796- for _ in 0 ..count {
2797- elements. push ( state. stack_pop ( ) ?) ;
2798- }
2799- elements. reverse ( ) ;
2829+ let elements = state. stack_pop_n ( count) ?;
28002830 state. stack_push ( fun. push_insn ( block, Insn :: NewArray { elements, state : exit_id } ) ) ;
28012831 }
28022832 YARVINSN_opt_newarray_send => {
28032833 let count = get_arg ( pc, 0 ) . as_usize ( ) ;
28042834 let method = get_arg ( pc, 1 ) . as_u32 ( ) ;
2805- let mut elements = vec ! [ ] ;
2806- for _ in 0 ..count {
2807- elements. push ( state. stack_pop ( ) ?) ;
2808- }
2809- elements. reverse ( ) ;
2835+ let elements = state. stack_pop_n ( count) ?;
28102836 let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
28112837 let ( bop, insn) = match method {
28122838 VM_OPT_NEWARRAY_SEND_MAX => ( BOP_MAX , Insn :: ArrayMax { elements, state : exit_id } ) ,
@@ -2871,13 +2897,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
28712897 }
28722898 YARVINSN_pushtoarray => {
28732899 let count = get_arg ( pc, 0 ) . as_usize ( ) ;
2874- let mut vals = vec ! [ ] ;
2875- for _ in 0 ..count {
2876- vals. push ( state. stack_pop ( ) ?) ;
2877- }
2900+ let vals = state. stack_pop_n ( count) ?;
28782901 let array = state. stack_pop ( ) ?;
28792902 let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
2880- for val in vals. into_iter ( ) . rev ( ) {
2903+ for val in vals. into_iter ( ) {
28812904 fun. push_insn ( block, Insn :: ArrayPush { array, val, state : exit_id } ) ;
28822905 }
28832906 state. stack_push ( array) ;
@@ -3079,12 +3102,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
30793102 }
30803103 let argc = unsafe { vm_ci_argc ( ( * cd) . ci ) } ;
30813104
3082- let mut args = vec ! [ ] ;
3083- for _ in 0 ..argc {
3084- args. push ( state. stack_pop ( ) ?) ;
3085- }
3086- args. reverse ( ) ;
3087-
3105+ let args = state. stack_pop_n ( argc as usize ) ?;
30883106 let recv = state. stack_pop ( ) ?;
30893107 let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
30903108 let send = fun. push_insn ( block, Insn :: SendWithoutBlock { self_val : recv, cd, args, state : exit_id } ) ;
@@ -3160,12 +3178,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
31603178 }
31613179 let argc = unsafe { vm_ci_argc ( ( * cd) . ci ) } ;
31623180
3163- let mut args = vec ! [ ] ;
3164- for _ in 0 ..argc {
3165- args. push ( state. stack_pop ( ) ?) ;
3166- }
3167- args. reverse ( ) ;
3168-
3181+ let args = state. stack_pop_n ( argc as usize ) ?;
31693182 let recv = state. stack_pop ( ) ?;
31703183 let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
31713184 let send = fun. push_insn ( block, Insn :: SendWithoutBlock { self_val : recv, cd, args, state : exit_id } ) ;
@@ -3183,12 +3196,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
31833196 }
31843197 let argc = unsafe { vm_ci_argc ( ( * cd) . ci ) } ;
31853198
3186- let mut args = vec ! [ ] ;
3187- for _ in 0 ..argc {
3188- args. push ( state. stack_pop ( ) ?) ;
3189- }
3190- args. reverse ( ) ;
3191-
3199+ let args = state. stack_pop_n ( argc as usize ) ?;
31923200 let recv = state. stack_pop ( ) ?;
31933201 let exit_id = fun. push_insn ( block, Insn :: Snapshot { state : exit_state } ) ;
31943202 let send = fun. push_insn ( block, Insn :: Send { self_val : recv, cd, blockiseq, args, state : exit_id } ) ;
@@ -5224,7 +5232,47 @@ mod tests {
52245232 v3:Fixnum[1] = Const Value(1)
52255233 v5:BasicObject = ObjToString v3
52265234 v7:String = AnyToString v3, str: v5
5227- SideExit UnknownOpcode(concatstrings)
5235+ v9:StringExact = StringConcat v2, v7
5236+ Return v9
5237+ "# ] ] ) ;
5238+ }
5239+
5240+ #[ test]
5241+ fn test_string_concat ( ) {
5242+ eval ( r##"
5243+ def test = "#{1}#{2}#{3}"
5244+ "## ) ;
5245+ assert_method_hir_with_opcode ( "test" , YARVINSN_concatstrings , expect ! [ [ r#"
5246+ fn test@<compiled>:2:
5247+ bb0(v0:BasicObject):
5248+ v2:Fixnum[1] = Const Value(1)
5249+ v4:BasicObject = ObjToString v2
5250+ v6:String = AnyToString v2, str: v4
5251+ v7:Fixnum[2] = Const Value(2)
5252+ v9:BasicObject = ObjToString v7
5253+ v11:String = AnyToString v7, str: v9
5254+ v12:Fixnum[3] = Const Value(3)
5255+ v14:BasicObject = ObjToString v12
5256+ v16:String = AnyToString v12, str: v14
5257+ v18:StringExact = StringConcat v6, v11, v16
5258+ Return v18
5259+ "# ] ] ) ;
5260+ }
5261+
5262+ #[ test]
5263+ fn test_string_concat_empty ( ) {
5264+ eval ( r##"
5265+ def test = "#{}"
5266+ "## ) ;
5267+ assert_method_hir_with_opcode ( "test" , YARVINSN_concatstrings , expect ! [ [ r#"
5268+ fn test@<compiled>:2:
5269+ bb0(v0:BasicObject):
5270+ v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
5271+ v3:NilClass = Const Value(nil)
5272+ v5:BasicObject = ObjToString v3
5273+ v7:String = AnyToString v3, str: v5
5274+ v9:StringExact = StringConcat v2, v7
5275+ Return v9
52285276 "# ] ] ) ;
52295277 }
52305278
@@ -7172,7 +7220,8 @@ mod opt_tests {
71727220 v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
71737221 v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
71747222 v5:StringExact = StringCopy v3
7175- SideExit UnknownOpcode(concatstrings)
7223+ v11:StringExact = StringConcat v2, v5
7224+ Return v11
71767225 "# ] ] ) ;
71777226 }
71787227
@@ -7186,9 +7235,10 @@ mod opt_tests {
71867235 bb0(v0:BasicObject):
71877236 v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
71887237 v3:Fixnum[1] = Const Value(1)
7189- v10:BasicObject = SendWithoutBlock v3, :to_s
7190- v7:String = AnyToString v3, str: v10
7191- SideExit UnknownOpcode(concatstrings)
7238+ v11:BasicObject = SendWithoutBlock v3, :to_s
7239+ v7:String = AnyToString v3, str: v11
7240+ v9:StringExact = StringConcat v2, v7
7241+ Return v9
71927242 "# ] ] ) ;
71937243 }
71947244
0 commit comments