@@ -45,6 +45,67 @@ module YARV
4545 # RubyVM::InstructionSequence.compile("1 + 2").to_a
4646 #
4747 class Compiler < BasicVisitor
48+ # This represents a set of options that can be passed to the compiler to
49+ # control how it compiles the code. It mirrors the options that can be
50+ # passed to RubyVM::InstructionSequence.compile, except it only includes
51+ # options that actually change the behavior.
52+ class Options
53+ def initialize (
54+ frozen_string_literal : false ,
55+ inline_const_cache : true ,
56+ operands_unification : true ,
57+ peephole_optimization : true ,
58+ specialized_instruction : true ,
59+ tailcall_optimization : false
60+ )
61+ @frozen_string_literal = frozen_string_literal
62+ @inline_const_cache = inline_const_cache
63+ @operands_unification = operands_unification
64+ @peephole_optimization = peephole_optimization
65+ @specialized_instruction = specialized_instruction
66+ @tailcall_optimization = tailcall_optimization
67+ end
68+
69+ def to_hash
70+ {
71+ frozen_string_literal : @frozen_string_literal ,
72+ inline_const_cache : @inline_const_cache ,
73+ operands_unification : @operands_unification ,
74+ peephole_optimization : @peephole_optimization ,
75+ specialized_instruction : @specialized_instruction ,
76+ tailcall_optimization : @tailcall_optimization
77+ }
78+ end
79+
80+ def frozen_string_literal!
81+ @frozen_string_literal = true
82+ end
83+
84+ def frozen_string_literal?
85+ @frozen_string_literal
86+ end
87+
88+ def inline_const_cache?
89+ @inline_const_cache
90+ end
91+
92+ def operands_unification?
93+ @operands_unification
94+ end
95+
96+ def peephole_optimization?
97+ @peephole_optimization
98+ end
99+
100+ def specialized_instruction?
101+ @specialized_instruction
102+ end
103+
104+ def tailcall_optimization?
105+ @tailcall_optimization
106+ end
107+ end
108+
48109 # This visitor is responsible for converting Syntax Tree nodes into their
49110 # corresponding Ruby structures. This is used to convert the operands of
50111 # some instructions like putobject that push a Ruby object directly onto
@@ -203,9 +264,7 @@ def visit_unsupported(_node)
203264
204265 # These options mirror the compilation options that we currently support
205266 # that can be also passed to RubyVM::InstructionSequence.compile.
206- attr_reader :frozen_string_literal ,
207- :operands_unification ,
208- :specialized_instruction
267+ attr_reader :options
209268
210269 # The current instruction sequence that is being compiled.
211270 attr_reader :iseq
@@ -215,15 +274,8 @@ def visit_unsupported(_node)
215274 # if we need to return the value of the last statement.
216275 attr_reader :last_statement
217276
218- def initialize (
219- frozen_string_literal : false ,
220- operands_unification : true ,
221- specialized_instruction : true
222- )
223- @frozen_string_literal = frozen_string_literal
224- @operands_unification = operands_unification
225- @specialized_instruction = specialized_instruction
226-
277+ def initialize ( options )
278+ @options = options
227279 @iseq = nil
228280 @last_statement = false
229281 end
@@ -233,7 +285,7 @@ def visit_BEGIN(node)
233285 end
234286
235287 def visit_CHAR ( node )
236- if frozen_string_literal
288+ if options . frozen_string_literal?
237289 iseq . putobject ( node . value [ 1 ..] )
238290 else
239291 iseq . putstring ( node . value [ 1 ..] )
@@ -279,8 +331,8 @@ def visit_aref(node)
279331 calldata = YARV . calldata ( :[] , 1 )
280332 visit ( node . collection )
281333
282- if !frozen_string_literal && specialized_instruction &&
283- ( node . index . parts . length == 1 )
334+ if !options . frozen_string_literal? &&
335+ options . specialized_instruction? && ( node . index . parts . length == 1 )
284336 arg = node . index . parts . first
285337
286338 if arg . is_a? ( StringLiteral ) && ( arg . parts . length == 1 )
@@ -450,7 +502,8 @@ def visit_assign(node)
450502 when ARefField
451503 calldata = YARV . calldata ( :[]= , 2 )
452504
453- if !frozen_string_literal && specialized_instruction &&
505+ if !options . frozen_string_literal? &&
506+ options . specialized_instruction? &&
454507 ( node . target . index . parts . length == 1 )
455508 arg = node . target . index . parts . first
456509
@@ -563,6 +616,9 @@ def visit_bare_assoc_hash(node)
563616 end
564617 end
565618
619+ def visit_begin ( node )
620+ end
621+
566622 def visit_binary ( node )
567623 case node . operator
568624 when :"&&"
@@ -624,6 +680,9 @@ def visit_bodystmt(node)
624680 visit ( node . statements )
625681 end
626682
683+ def visit_break ( node )
684+ end
685+
627686 def visit_call ( node )
628687 if node . is_a? ( CallNode )
629688 return (
@@ -678,12 +737,17 @@ def visit_call(node)
678737 end
679738 end
680739
740+ # Track whether or not this is a method call on a block proxy receiver.
741+ # If it is, we can potentially do tailcall optimizations on it.
742+ block_receiver = false
743+
681744 if node . receiver
682745 if node . receiver . is_a? ( VarRef )
683746 lookup = iseq . local_variable ( node . receiver . value . value . to_sym )
684747
685748 if lookup . local . is_a? ( LocalTable ::BlockLocal )
686749 iseq . getblockparamproxy ( lookup . index , lookup . level )
750+ block_receiver = true
687751 else
688752 visit ( node . receiver )
689753 end
@@ -714,6 +778,7 @@ def visit_call(node)
714778 when ArgsForward
715779 flag |= CallData ::CALL_ARGS_SPLAT
716780 flag |= CallData ::CALL_ARGS_BLOCKARG
781+ flag |= CallData ::CALL_TAILCALL if options . tailcall_optimization?
717782
718783 lookup = iseq . local_table . find ( :* )
719784 iseq . getlocal ( lookup . index , lookup . level )
@@ -730,9 +795,22 @@ def visit_call(node)
730795 end
731796
732797 block_iseq = visit ( node . block ) if node . block
798+
799+ # If there's no block and we don't already have any special flags set,
800+ # then we can safely call this simple arguments. Note that has to be the
801+ # first flag we set after looking at the arguments to get the flags
802+ # correct.
733803 flag |= CallData ::CALL_ARGS_SIMPLE if block_iseq . nil? && flag == 0
804+
805+ # If there's no receiver, then this is an "fcall".
734806 flag |= CallData ::CALL_FCALL if node . receiver . nil?
735807
808+ # If we're calling a method on the passed block object and we have
809+ # tailcall optimizations turned on, then we can set the tailcall flag.
810+ if block_receiver && options . tailcall_optimization?
811+ flag |= CallData ::CALL_TAILCALL
812+ end
813+
736814 iseq . send (
737815 YARV . calldata ( node . message . value . to_sym , argc , flag ) ,
738816 block_iseq
@@ -952,6 +1030,9 @@ def visit_elsif(node)
9521030 )
9531031 end
9541032
1033+ def visit_ensure ( node )
1034+ end
1035+
9551036 def visit_field ( node )
9561037 visit ( node . parent )
9571038 end
@@ -960,6 +1041,9 @@ def visit_float(node)
9601041 iseq . putobject ( node . accept ( RubyVisitor . new ) )
9611042 end
9621043
1044+ def visit_fndptn ( node )
1045+ end
1046+
9631047 def visit_for ( node )
9641048 visit ( node . collection )
9651049
@@ -1000,6 +1084,9 @@ def visit_hash(node)
10001084 end
10011085 end
10021086
1087+ def visit_hshptn ( node )
1088+ end
1089+
10031090 def visit_heredoc ( node )
10041091 if node . beginning . value . end_with? ( "`" )
10051092 visit_xstring_literal ( node )
@@ -1079,6 +1166,9 @@ def visit_imaginary(node)
10791166 iseq . putobject ( node . accept ( RubyVisitor . new ) )
10801167 end
10811168
1169+ def visit_in ( node )
1170+ end
1171+
10821172 def visit_int ( node )
10831173 iseq . putobject ( node . accept ( RubyVisitor . new ) )
10841174 end
@@ -1179,6 +1269,9 @@ def visit_mrhs(node)
11791269 end
11801270 end
11811271
1272+ def visit_next ( node )
1273+ end
1274+
11821275 def visit_not ( node )
11831276 visit ( node . statement )
11841277 iseq . send ( YARV . calldata ( :! ) )
@@ -1344,12 +1437,18 @@ def visit_paren(node)
13441437 visit ( node . contents )
13451438 end
13461439
1440+ def visit_pinned_begin ( node )
1441+ end
1442+
1443+ def visit_pinned_var_ref ( node )
1444+ end
1445+
13471446 def visit_program ( node )
13481447 node . statements . body . each do |statement |
13491448 break unless statement . is_a? ( Comment )
13501449
13511450 if statement . value == "# frozen_string_literal: true"
1352- @ frozen_string_literal = true
1451+ options . frozen_string_literal!
13531452 end
13541453 end
13551454
@@ -1373,11 +1472,8 @@ def visit_program(node)
13731472 "<compiled>" ,
13741473 nil ,
13751474 node . location ,
1376- frozen_string_literal : frozen_string_literal ,
1377- operands_unification : operands_unification ,
1378- specialized_instruction : specialized_instruction
1475+ options
13791476 )
1380-
13811477 with_child_iseq ( top_iseq ) do
13821478 visit_all ( preexes )
13831479
@@ -1398,7 +1494,7 @@ def visit_qsymbols(node)
13981494 end
13991495
14001496 def visit_qwords ( node )
1401- if frozen_string_literal
1497+ if options . frozen_string_literal?
14021498 iseq . duparray ( node . accept ( RubyVisitor . new ) )
14031499 else
14041500 visit_all ( node . elements )
@@ -1512,6 +1608,9 @@ def visit_rational(node)
15121608 iseq . putobject ( node . accept ( RubyVisitor . new ) )
15131609 end
15141610
1611+ def visit_redo ( node )
1612+ end
1613+
15151614 def visit_regexp_literal ( node )
15161615 if ( compiled = RubyVisitor . compile ( node ) )
15171616 iseq . putobject ( compiled )
@@ -1522,12 +1621,27 @@ def visit_regexp_literal(node)
15221621 end
15231622 end
15241623
1624+ def visit_rescue ( node )
1625+ end
1626+
1627+ def visit_rescue_ex ( node )
1628+ end
1629+
1630+ def visit_rescue_mod ( node )
1631+ end
1632+
15251633 def visit_rest_param ( node )
15261634 iseq . local_table . plain ( node . name . value . to_sym )
15271635 iseq . argument_options [ :rest_start ] = iseq . argument_size
15281636 iseq . argument_size += 1
15291637 end
15301638
1639+ def visit_retry ( node )
1640+ end
1641+
1642+ def visit_return ( node )
1643+ end
1644+
15311645 def visit_sclass ( node )
15321646 visit ( node . target )
15331647 iseq . putnil
@@ -1628,7 +1742,7 @@ def visit_top_const_ref(node)
16281742 end
16291743
16301744 def visit_tstring_content ( node )
1631- if frozen_string_literal
1745+ if options . frozen_string_literal?
16321746 iseq . putobject ( node . accept ( RubyVisitor . new ) )
16331747 else
16341748 iseq . putstring ( node . accept ( RubyVisitor . new ) )
@@ -1804,7 +1918,8 @@ def visit_word(node)
18041918 end
18051919
18061920 def visit_words ( node )
1807- if frozen_string_literal && ( compiled = RubyVisitor . compile ( node ) )
1921+ if options . frozen_string_literal? &&
1922+ ( compiled = RubyVisitor . compile ( node ) )
18081923 iseq . duparray ( compiled )
18091924 else
18101925 visit_all ( node . elements )
0 commit comments