@@ -45,6 +45,67 @@ module YARV
45
45
# RubyVM::InstructionSequence.compile("1 + 2").to_a
46
46
#
47
47
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
+
48
109
# This visitor is responsible for converting Syntax Tree nodes into their
49
110
# corresponding Ruby structures. This is used to convert the operands of
50
111
# some instructions like putobject that push a Ruby object directly onto
@@ -203,9 +264,7 @@ def visit_unsupported(_node)
203
264
204
265
# These options mirror the compilation options that we currently support
205
266
# 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
209
268
210
269
# The current instruction sequence that is being compiled.
211
270
attr_reader :iseq
@@ -215,15 +274,8 @@ def visit_unsupported(_node)
215
274
# if we need to return the value of the last statement.
216
275
attr_reader :last_statement
217
276
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
227
279
@iseq = nil
228
280
@last_statement = false
229
281
end
@@ -233,7 +285,7 @@ def visit_BEGIN(node)
233
285
end
234
286
235
287
def visit_CHAR ( node )
236
- if frozen_string_literal
288
+ if options . frozen_string_literal?
237
289
iseq . putobject ( node . value [ 1 ..] )
238
290
else
239
291
iseq . putstring ( node . value [ 1 ..] )
@@ -279,8 +331,8 @@ def visit_aref(node)
279
331
calldata = YARV . calldata ( :[] , 1 )
280
332
visit ( node . collection )
281
333
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 )
284
336
arg = node . index . parts . first
285
337
286
338
if arg . is_a? ( StringLiteral ) && ( arg . parts . length == 1 )
@@ -450,7 +502,8 @@ def visit_assign(node)
450
502
when ARefField
451
503
calldata = YARV . calldata ( :[]= , 2 )
452
504
453
- if !frozen_string_literal && specialized_instruction &&
505
+ if !options . frozen_string_literal? &&
506
+ options . specialized_instruction? &&
454
507
( node . target . index . parts . length == 1 )
455
508
arg = node . target . index . parts . first
456
509
@@ -563,6 +616,9 @@ def visit_bare_assoc_hash(node)
563
616
end
564
617
end
565
618
619
+ def visit_begin ( node )
620
+ end
621
+
566
622
def visit_binary ( node )
567
623
case node . operator
568
624
when :"&&"
@@ -624,6 +680,9 @@ def visit_bodystmt(node)
624
680
visit ( node . statements )
625
681
end
626
682
683
+ def visit_break ( node )
684
+ end
685
+
627
686
def visit_call ( node )
628
687
if node . is_a? ( CallNode )
629
688
return (
@@ -678,12 +737,17 @@ def visit_call(node)
678
737
end
679
738
end
680
739
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
+
681
744
if node . receiver
682
745
if node . receiver . is_a? ( VarRef )
683
746
lookup = iseq . local_variable ( node . receiver . value . value . to_sym )
684
747
685
748
if lookup . local . is_a? ( LocalTable ::BlockLocal )
686
749
iseq . getblockparamproxy ( lookup . index , lookup . level )
750
+ block_receiver = true
687
751
else
688
752
visit ( node . receiver )
689
753
end
@@ -714,6 +778,7 @@ def visit_call(node)
714
778
when ArgsForward
715
779
flag |= CallData ::CALL_ARGS_SPLAT
716
780
flag |= CallData ::CALL_ARGS_BLOCKARG
781
+ flag |= CallData ::CALL_TAILCALL if options . tailcall_optimization?
717
782
718
783
lookup = iseq . local_table . find ( :* )
719
784
iseq . getlocal ( lookup . index , lookup . level )
@@ -730,9 +795,22 @@ def visit_call(node)
730
795
end
731
796
732
797
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.
733
803
flag |= CallData ::CALL_ARGS_SIMPLE if block_iseq . nil? && flag == 0
804
+
805
+ # If there's no receiver, then this is an "fcall".
734
806
flag |= CallData ::CALL_FCALL if node . receiver . nil?
735
807
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
+
736
814
iseq . send (
737
815
YARV . calldata ( node . message . value . to_sym , argc , flag ) ,
738
816
block_iseq
@@ -952,6 +1030,9 @@ def visit_elsif(node)
952
1030
)
953
1031
end
954
1032
1033
+ def visit_ensure ( node )
1034
+ end
1035
+
955
1036
def visit_field ( node )
956
1037
visit ( node . parent )
957
1038
end
@@ -960,6 +1041,9 @@ def visit_float(node)
960
1041
iseq . putobject ( node . accept ( RubyVisitor . new ) )
961
1042
end
962
1043
1044
+ def visit_fndptn ( node )
1045
+ end
1046
+
963
1047
def visit_for ( node )
964
1048
visit ( node . collection )
965
1049
@@ -1000,6 +1084,9 @@ def visit_hash(node)
1000
1084
end
1001
1085
end
1002
1086
1087
+ def visit_hshptn ( node )
1088
+ end
1089
+
1003
1090
def visit_heredoc ( node )
1004
1091
if node . beginning . value . end_with? ( "`" )
1005
1092
visit_xstring_literal ( node )
@@ -1079,6 +1166,9 @@ def visit_imaginary(node)
1079
1166
iseq . putobject ( node . accept ( RubyVisitor . new ) )
1080
1167
end
1081
1168
1169
+ def visit_in ( node )
1170
+ end
1171
+
1082
1172
def visit_int ( node )
1083
1173
iseq . putobject ( node . accept ( RubyVisitor . new ) )
1084
1174
end
@@ -1179,6 +1269,9 @@ def visit_mrhs(node)
1179
1269
end
1180
1270
end
1181
1271
1272
+ def visit_next ( node )
1273
+ end
1274
+
1182
1275
def visit_not ( node )
1183
1276
visit ( node . statement )
1184
1277
iseq . send ( YARV . calldata ( :! ) )
@@ -1344,12 +1437,18 @@ def visit_paren(node)
1344
1437
visit ( node . contents )
1345
1438
end
1346
1439
1440
+ def visit_pinned_begin ( node )
1441
+ end
1442
+
1443
+ def visit_pinned_var_ref ( node )
1444
+ end
1445
+
1347
1446
def visit_program ( node )
1348
1447
node . statements . body . each do |statement |
1349
1448
break unless statement . is_a? ( Comment )
1350
1449
1351
1450
if statement . value == "# frozen_string_literal: true"
1352
- @ frozen_string_literal = true
1451
+ options . frozen_string_literal!
1353
1452
end
1354
1453
end
1355
1454
@@ -1373,11 +1472,8 @@ def visit_program(node)
1373
1472
"<compiled>" ,
1374
1473
nil ,
1375
1474
node . location ,
1376
- frozen_string_literal : frozen_string_literal ,
1377
- operands_unification : operands_unification ,
1378
- specialized_instruction : specialized_instruction
1475
+ options
1379
1476
)
1380
-
1381
1477
with_child_iseq ( top_iseq ) do
1382
1478
visit_all ( preexes )
1383
1479
@@ -1398,7 +1494,7 @@ def visit_qsymbols(node)
1398
1494
end
1399
1495
1400
1496
def visit_qwords ( node )
1401
- if frozen_string_literal
1497
+ if options . frozen_string_literal?
1402
1498
iseq . duparray ( node . accept ( RubyVisitor . new ) )
1403
1499
else
1404
1500
visit_all ( node . elements )
@@ -1512,6 +1608,9 @@ def visit_rational(node)
1512
1608
iseq . putobject ( node . accept ( RubyVisitor . new ) )
1513
1609
end
1514
1610
1611
+ def visit_redo ( node )
1612
+ end
1613
+
1515
1614
def visit_regexp_literal ( node )
1516
1615
if ( compiled = RubyVisitor . compile ( node ) )
1517
1616
iseq . putobject ( compiled )
@@ -1522,12 +1621,27 @@ def visit_regexp_literal(node)
1522
1621
end
1523
1622
end
1524
1623
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
+
1525
1633
def visit_rest_param ( node )
1526
1634
iseq . local_table . plain ( node . name . value . to_sym )
1527
1635
iseq . argument_options [ :rest_start ] = iseq . argument_size
1528
1636
iseq . argument_size += 1
1529
1637
end
1530
1638
1639
+ def visit_retry ( node )
1640
+ end
1641
+
1642
+ def visit_return ( node )
1643
+ end
1644
+
1531
1645
def visit_sclass ( node )
1532
1646
visit ( node . target )
1533
1647
iseq . putnil
@@ -1628,7 +1742,7 @@ def visit_top_const_ref(node)
1628
1742
end
1629
1743
1630
1744
def visit_tstring_content ( node )
1631
- if frozen_string_literal
1745
+ if options . frozen_string_literal?
1632
1746
iseq . putobject ( node . accept ( RubyVisitor . new ) )
1633
1747
else
1634
1748
iseq . putstring ( node . accept ( RubyVisitor . new ) )
@@ -1804,7 +1918,8 @@ def visit_word(node)
1804
1918
end
1805
1919
1806
1920
def visit_words ( node )
1807
- if frozen_string_literal && ( compiled = RubyVisitor . compile ( node ) )
1921
+ if options . frozen_string_literal? &&
1922
+ ( compiled = RubyVisitor . compile ( node ) )
1808
1923
iseq . duparray ( compiled )
1809
1924
else
1810
1925
visit_all ( node . elements )
0 commit comments