Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 42a15a7

Browse files
authored
Merge pull request #216 from ruby-syntax-tree/more-options
More options
2 parents 04bedf4 + a43005d commit 42a15a7

File tree

5 files changed

+514
-74
lines changed

5 files changed

+514
-74
lines changed

lib/syntax_tree/yarv.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ module SyntaxTree
44
# This module provides an object representation of the YARV bytecode.
55
module YARV
66
# Compile the given source into a YARV instruction sequence.
7-
def self.compile(source, **options)
8-
SyntaxTree.parse(source).accept(Compiler.new(**options))
7+
def self.compile(source, options = Compiler::Options.new)
8+
SyntaxTree.parse(source).accept(Compiler.new(options))
99
end
1010
end
1111
end

lib/syntax_tree/yarv/compiler.rb

Lines changed: 139 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)