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

Commit a43005d

Browse files
committed
Allow converting from compiled iseq to YARV iseq
1 parent 83cdfbb commit a43005d

File tree

4 files changed

+267
-12
lines changed

4 files changed

+267
-12
lines changed

lib/syntax_tree/yarv/compiler.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ def visit_aref(node)
331331
calldata = YARV.calldata(:[], 1)
332332
visit(node.collection)
333333

334-
if !options.frozen_string_literal? && options.specialized_instruction? &&
335-
(node.index.parts.length == 1)
334+
if !options.frozen_string_literal? &&
335+
options.specialized_instruction? && (node.index.parts.length == 1)
336336
arg = node.index.parts.first
337337

338338
if arg.is_a?(StringLiteral) && (arg.parts.length == 1)
@@ -502,7 +502,8 @@ def visit_assign(node)
502502
when ARefField
503503
calldata = YARV.calldata(:[]=, 2)
504504

505-
if !options.frozen_string_literal? && options.specialized_instruction? &&
505+
if !options.frozen_string_literal? &&
506+
options.specialized_instruction? &&
506507
(node.target.index.parts.length == 1)
507508
arg = node.target.index.parts.first
508509

@@ -1085,7 +1086,7 @@ def visit_hash(node)
10851086

10861087
def visit_hshptn(node)
10871088
end
1088-
1089+
10891090
def visit_heredoc(node)
10901091
if node.beginning.value.end_with?("`")
10911092
visit_xstring_literal(node)
@@ -1465,7 +1466,14 @@ def visit_program(node)
14651466
end
14661467
end
14671468

1468-
top_iseq = InstructionSequence.new(:top, "<compiled>", nil, node.location, options)
1469+
top_iseq =
1470+
InstructionSequence.new(
1471+
:top,
1472+
"<compiled>",
1473+
nil,
1474+
node.location,
1475+
options
1476+
)
14691477
with_child_iseq(top_iseq) do
14701478
visit_all(preexes)
14711479

@@ -1910,7 +1918,8 @@ def visit_word(node)
19101918
end
19111919

19121920
def visit_words(node)
1913-
if options.frozen_string_literal? && (compiled = RubyVisitor.compile(node))
1921+
if options.frozen_string_literal? &&
1922+
(compiled = RubyVisitor.compile(node))
19141923
iseq.duparray(compiled)
19151924
else
19161925
visit_all(node.elements)

lib/syntax_tree/yarv/instruction_sequence.rb

Lines changed: 223 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ def change_by(value)
7878
# These are various compilation options provided.
7979
attr_reader :options
8080

81-
def initialize(type, name, parent_iseq, location, options = Compiler::Options.new)
81+
def initialize(
82+
type,
83+
name,
84+
parent_iseq,
85+
location,
86+
options = Compiler::Options.new
87+
)
8288
@type = type
8389
@name = name
8490
@parent_iseq = parent_iseq
@@ -413,6 +419,10 @@ def opt_aset_with(object, calldata)
413419
push(OptAsetWith.new(object, calldata))
414420
end
415421

422+
def opt_case_dispatch(case_dispatch_hash, else_label)
423+
push(OptCaseDispatch.new(case_dispatch_hash, else_label))
424+
end
425+
416426
def opt_getconstant_path(names)
417427
if RUBY_VERSION < "3.2" || !options.inline_const_cache?
418428
cache = nil
@@ -655,13 +665,225 @@ def swap
655665
push(Swap.new)
656666
end
657667

668+
def throw(type)
669+
push(Throw.new(type))
670+
end
671+
658672
def topn(number)
659673
push(TopN.new(number))
660674
end
661675

662676
def toregexp(options, length)
663677
push(ToRegExp.new(options, length))
664678
end
679+
680+
# This method will create a new instruction sequence from a serialized
681+
# RubyVM::InstructionSequence object.
682+
def self.from(source, options = Compiler::Options.new, parent_iseq = nil)
683+
iseq = new(source[9], source[5], parent_iseq, Location.default, options)
684+
685+
# set up the correct argument size
686+
iseq.argument_size = source[4][:arg_size]
687+
688+
# set up all of the locals
689+
source[10].each { |local| iseq.local_table.plain(local) }
690+
691+
# set up the argument options
692+
iseq.argument_options.merge!(source[11])
693+
694+
# set up all of the instructions
695+
source[13].each do |insn|
696+
# skip line numbers
697+
next if insn.is_a?(Integer)
698+
699+
# put events into the array and then continue
700+
if insn.is_a?(Symbol)
701+
iseq.event(insn)
702+
next
703+
end
704+
705+
type, *opnds = insn
706+
case type
707+
when :adjuststack
708+
iseq.adjuststack(opnds[0])
709+
when :anytostring
710+
iseq.anytostring
711+
when :branchif
712+
iseq.branchif(opnds[0])
713+
when :branchnil
714+
iseq.branchnil(opnds[0])
715+
when :branchunless
716+
iseq.branchunless(opnds[0])
717+
when :checkkeyword
718+
iseq.checkkeyword(iseq.local_table.size - opnds[0] + 2, opnds[1])
719+
when :checkmatch
720+
iseq.checkmatch(opnds[0])
721+
when :checktype
722+
iseq.checktype(opnds[0])
723+
when :concatarray
724+
iseq.concatarray
725+
when :concatstrings
726+
iseq.concatstrings(opnds[0])
727+
when :defineclass
728+
iseq.defineclass(opnds[0], from(opnds[1], options, iseq), opnds[2])
729+
when :defined
730+
iseq.defined(opnds[0], opnds[1], opnds[2])
731+
when :definemethod
732+
iseq.definemethod(opnds[0], from(opnds[1], options, iseq))
733+
when :definesmethod
734+
iseq.definesmethod(opnds[0], from(opnds[1], options, iseq))
735+
when :dup
736+
iseq.dup
737+
when :duparray
738+
iseq.duparray(opnds[0])
739+
when :duphash
740+
iseq.duphash(opnds[0])
741+
when :dupn
742+
iseq.dupn(opnds[0])
743+
when :expandarray
744+
iseq.expandarray(opnds[0], opnds[1])
745+
when :getblockparam, :getblockparamproxy, :getlocal, :getlocal_WC_0,
746+
:getlocal_WC_1, :setblockparam, :setlocal, :setlocal_WC_0,
747+
:setlocal_WC_1
748+
current = iseq
749+
level = 0
750+
751+
case type
752+
when :getlocal_WC_1, :setlocal_WC_1
753+
level = 1
754+
when :getblockparam, :getblockparamproxy, :getlocal, :setblockparam,
755+
:setlocal
756+
level = opnds[1]
757+
end
758+
759+
level.times { current = current.parent_iseq }
760+
index = current.local_table.size - opnds[0] + 2
761+
762+
case type
763+
when :getblockparam
764+
iseq.getblockparam(index, level)
765+
when :getblockparamproxy
766+
iseq.getblockparamproxy(index, level)
767+
when :getlocal, :getlocal_WC_0, :getlocal_WC_1
768+
iseq.getlocal(index, level)
769+
when :setblockparam
770+
iseq.setblockparam(index, level)
771+
when :setlocal, :setlocal_WC_0, :setlocal_WC_1
772+
iseq.setlocal(index, level)
773+
end
774+
when :getclassvariable
775+
iseq.push(GetClassVariable.new(opnds[0], opnds[1]))
776+
when :getconstant
777+
iseq.getconstant(opnds[0])
778+
when :getglobal
779+
iseq.getglobal(opnds[0])
780+
when :getinstancevariable
781+
iseq.push(GetInstanceVariable.new(opnds[0], opnds[1]))
782+
when :getspecial
783+
iseq.getspecial(opnds[0], opnds[1])
784+
when :intern
785+
iseq.intern
786+
when :invokeblock
787+
iseq.invokeblock(CallData.from(opnds[0]))
788+
when :invokesuper
789+
block_iseq = opnds[1] ? from(opnds[1], options, iseq) : nil
790+
iseq.invokesuper(CallData.from(opnds[0]), block_iseq)
791+
when :jump
792+
iseq.jump(opnds[0])
793+
when :leave
794+
iseq.leave
795+
when :newarray
796+
iseq.newarray(opnds[0])
797+
when :newarraykwsplat
798+
iseq.newarraykwsplat(opnds[0])
799+
when :newhash
800+
iseq.newhash(opnds[0])
801+
when :newrange
802+
iseq.newrange(opnds[0])
803+
when :nop
804+
iseq.nop
805+
when :objtostring
806+
iseq.objtostring(CallData.from(opnds[0]))
807+
when :once
808+
iseq.once(from(opnds[0], options, iseq), opnds[1])
809+
when :opt_and, :opt_aref, :opt_aset, :opt_div, :opt_empty_p, :opt_eq,
810+
:opt_ge, :opt_gt, :opt_le, :opt_length, :opt_lt, :opt_ltlt,
811+
:opt_minus, :opt_mod, :opt_mult, :opt_nil_p, :opt_not, :opt_or,
812+
:opt_plus, :opt_regexpmatch2, :opt_send_without_block, :opt_size,
813+
:opt_succ
814+
iseq.send(CallData.from(opnds[0]), nil)
815+
when :opt_aref_with
816+
iseq.opt_aref_with(opnds[0], CallData.from(opnds[1]))
817+
when :opt_aset_with
818+
iseq.opt_aset_with(opnds[0], CallData.from(opnds[1]))
819+
when :opt_case_dispatch
820+
iseq.opt_case_dispatch(opnds[0], opnds[1])
821+
when :opt_getconstant_path
822+
iseq.opt_getconstant_path(opnds[0])
823+
when :opt_getinlinecache
824+
iseq.opt_getinlinecache(opnds[0], opnds[1])
825+
when :opt_newarray_max
826+
iseq.opt_newarray_max(opnds[0])
827+
when :opt_newarray_min
828+
iseq.opt_newarray_min(opnds[0])
829+
when :opt_neq
830+
iseq.push(
831+
OptNEq.new(CallData.from(opnds[0]), CallData.from(opnds[1]))
832+
)
833+
when :opt_setinlinecache
834+
iseq.opt_setinlinecache(opnds[0])
835+
when :opt_str_freeze
836+
iseq.opt_str_freeze(opnds[0])
837+
when :opt_str_uminus
838+
iseq.opt_str_uminus(opnds[0])
839+
when :pop
840+
iseq.pop
841+
when :putnil
842+
iseq.putnil
843+
when :putobject
844+
iseq.putobject(opnds[0])
845+
when :putobject_INT2FIX_0_
846+
iseq.putobject(0)
847+
when :putobject_INT2FIX_1_
848+
iseq.putobject(1)
849+
when :putself
850+
iseq.putself
851+
when :putstring
852+
iseq.putstring(opnds[0])
853+
when :putspecialobject
854+
iseq.putspecialobject(opnds[0])
855+
when :send
856+
block_iseq = opnds[1] ? from(opnds[1], options, iseq) : nil
857+
iseq.send(CallData.from(opnds[0]), block_iseq)
858+
when :setclassvariable
859+
iseq.push(SetClassVariable.new(opnds[0], opnds[1]))
860+
when :setconstant
861+
iseq.setconstant(opnds[0])
862+
when :setglobal
863+
iseq.setglobal(opnds[0])
864+
when :setinstancevariable
865+
iseq.push(SetInstanceVariable.new(opnds[0], opnds[1]))
866+
when :setn
867+
iseq.setn(opnds[0])
868+
when :setspecial
869+
iseq.setspecial(opnds[0])
870+
when :splatarray
871+
iseq.splatarray(opnds[0])
872+
when :swap
873+
iseq.swap
874+
when :throw
875+
iseq.throw(opnds[0])
876+
when :topn
877+
iseq.topn(opnds[0])
878+
when :toregexp
879+
iseq.toregexp(opnds[0], opnds[1])
880+
else
881+
raise "Unknown instruction type: #{type}"
882+
end
883+
end
884+
885+
iseq
886+
end
665887
end
666888
end
667889
end

lib/syntax_tree/yarv/instructions.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ def to_h
4242
result[:kw_arg] = kw_arg if kw_arg
4343
result
4444
end
45+
46+
def self.from(serialized)
47+
new(
48+
serialized[:mid],
49+
serialized[:orig_argc],
50+
serialized[:flag],
51+
serialized[:kw_arg]
52+
)
53+
end
4554
end
4655

4756
# A convenience method for creating a CallData object.

test/compiler_test.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -439,12 +439,16 @@ class CompilerTest < Minitest::Test
439439
]
440440

441441
OPTIONS.each do |options|
442-
suffix = options.to_hash.map { |k, v| "#{k}=#{v}" }.join("&")
442+
suffix = options.to_hash.map { |key, value| "#{key}=#{value}" }.join("&")
443443

444444
CASES.each do |source|
445-
define_method(:"test_#{source}_(#{suffix})") do
445+
define_method(:"test_compiles_#{source}_(#{suffix})") do
446446
assert_compiles(source, options)
447447
end
448+
449+
define_method(:"test_loads_#{source}_(#{suffix})") do
450+
assert_loads(source, options)
451+
end
448452
end
449453
end
450454

@@ -483,12 +487,23 @@ def serialize_iseq(iseq)
483487
serialized
484488
end
485489

490+
# Check that the compiled instruction sequence matches the expected
491+
# instruction sequence.
486492
def assert_compiles(source, options)
487-
program = SyntaxTree.parse(source)
488-
489493
assert_equal(
490494
serialize_iseq(RubyVM::InstructionSequence.compile(source, **options)),
491-
serialize_iseq(program.accept(YARV::Compiler.new(options)))
495+
serialize_iseq(YARV.compile(source, options))
496+
)
497+
end
498+
499+
# Check that the compiled instruction sequence matches the instruction
500+
# sequence created directly from the compiled instruction sequence.
501+
def assert_loads(source, options)
502+
compiled = RubyVM::InstructionSequence.compile(source, **options)
503+
504+
assert_equal(
505+
serialize_iseq(compiled),
506+
serialize_iseq(YARV::InstructionSequence.from(compiled.to_a, options))
492507
)
493508
end
494509

0 commit comments

Comments
 (0)