syntax_tree 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SyntaxTree
4
+ # This module provides an object representation of the YARV bytecode.
5
+ module YARV
6
+ # This class is meant to mirror RubyVM::InstructionSequence. It contains a
7
+ # list of instructions along with the metadata pertaining to them. It also
8
+ # functions as a builder for the instruction sequence.
9
+ class InstructionSequence
10
+ # When the list of instructions is first being created, it's stored as a
11
+ # linked list. This is to make it easier to perform peephole optimizations
12
+ # and other transformations like instruction specialization.
13
+ class InstructionList
14
+ class Node
15
+ attr_accessor :value, :next_node
16
+
17
+ def initialize(value, next_node = nil)
18
+ @value = value
19
+ @next_node = next_node
20
+ end
21
+ end
22
+
23
+ include Enumerable
24
+ attr_reader :head_node, :tail_node
25
+
26
+ def initialize
27
+ @head_node = nil
28
+ @tail_node = nil
29
+ end
30
+
31
+ def each
32
+ return to_enum(__method__) unless block_given?
33
+ each_node { |node| yield node.value }
34
+ end
35
+
36
+ def each_node
37
+ return to_enum(__method__) unless block_given?
38
+ node = head_node
39
+
40
+ while node
41
+ yield node, node.value
42
+ node = node.next_node
43
+ end
44
+ end
45
+
46
+ def push(instruction)
47
+ node = Node.new(instruction)
48
+
49
+ if head_node.nil?
50
+ @head_node = node
51
+ @tail_node = node
52
+ else
53
+ @tail_node.next_node = node
54
+ @tail_node = node
55
+ end
56
+
57
+ node
58
+ end
59
+ end
60
+
61
+ MAGIC = "YARVInstructionSequence/SimpleDataFormat"
62
+
63
+ # This provides a handle to the rb_iseq_load function, which allows you to
64
+ # pass a serialized iseq to Ruby and have it return a
65
+ # RubyVM::InstructionSequence object.
66
+ ISEQ_LOAD =
67
+ begin
68
+ Fiddle::Function.new(
69
+ Fiddle::Handle::DEFAULT["rb_iseq_load"],
70
+ [Fiddle::TYPE_VOIDP] * 3,
71
+ Fiddle::TYPE_VOIDP
72
+ )
73
+ rescue NameError
74
+ end
75
+
76
+ # This object is used to track the size of the stack at any given time. It
77
+ # is effectively a mini symbolic interpreter. It's necessary because when
78
+ # instruction sequences get serialized they include a :stack_max field on
79
+ # them. This field is used to determine how much stack space to allocate
80
+ # for the instruction sequence.
81
+ class Stack
82
+ attr_reader :current_size, :maximum_size
83
+
84
+ def initialize
85
+ @current_size = 0
86
+ @maximum_size = 0
87
+ end
88
+
89
+ def change_by(value)
90
+ @current_size += value
91
+ @maximum_size = @current_size if @current_size > @maximum_size
92
+ end
93
+ end
94
+
95
+ # This represents the destination of instructions that jump. Initially it
96
+ # does not track its position so that when we perform optimizations the
97
+ # indices don't get messed up.
98
+ class Label
99
+ attr_reader :name
100
+
101
+ # When we're serializing the instruction sequence, we need to be able to
102
+ # look up the label from the branch instructions and then access the
103
+ # subsequent node. So we'll store the reference here.
104
+ attr_accessor :node
105
+
106
+ def initialize(name = nil)
107
+ @name = name
108
+ end
109
+
110
+ def patch!(name)
111
+ @name = name
112
+ end
113
+
114
+ def inspect
115
+ name.inspect
116
+ end
117
+ end
118
+
119
+ # The type of the instruction sequence.
120
+ attr_reader :type
121
+
122
+ # The name of the instruction sequence.
123
+ attr_reader :name
124
+
125
+ # The parent instruction sequence, if there is one.
126
+ attr_reader :parent_iseq
127
+
128
+ # The location of the root node of this instruction sequence.
129
+ attr_reader :location
130
+
131
+ # This is the list of information about the arguments to this
132
+ # instruction sequence.
133
+ attr_accessor :argument_size
134
+ attr_reader :argument_options
135
+
136
+ # The catch table for this instruction sequence.
137
+ attr_reader :catch_table
138
+
139
+ # The list of instructions for this instruction sequence.
140
+ attr_reader :insns
141
+
142
+ # The table of local variables.
143
+ attr_reader :local_table
144
+
145
+ # The hash of names of instance and class variables pointing to the
146
+ # index of their associated inline storage.
147
+ attr_reader :inline_storages
148
+
149
+ # The index of the next inline storage that will be created.
150
+ attr_reader :storage_index
151
+
152
+ # An object that will track the current size of the stack and the
153
+ # maximum size of the stack for this instruction sequence.
154
+ attr_reader :stack
155
+
156
+ # These are various compilation options provided.
157
+ attr_reader :options
158
+
159
+ def initialize(
160
+ type,
161
+ name,
162
+ parent_iseq,
163
+ location,
164
+ options = Compiler::Options.new
165
+ )
166
+ @type = type
167
+ @name = name
168
+ @parent_iseq = parent_iseq
169
+ @location = location
170
+
171
+ @argument_size = 0
172
+ @argument_options = {}
173
+ @catch_table = []
174
+
175
+ @local_table = LocalTable.new
176
+ @inline_storages = {}
177
+ @insns = InstructionList.new
178
+ @storage_index = 0
179
+ @stack = Stack.new
180
+
181
+ @options = options
182
+ end
183
+
184
+ ##########################################################################
185
+ # Query methods
186
+ ##########################################################################
187
+
188
+ def local_variable(name, level = 0)
189
+ if (lookup = local_table.find(name, level))
190
+ lookup
191
+ elsif parent_iseq
192
+ parent_iseq.local_variable(name, level + 1)
193
+ end
194
+ end
195
+
196
+ def inline_storage
197
+ storage = storage_index
198
+ @storage_index += 1
199
+ storage
200
+ end
201
+
202
+ def inline_storage_for(name)
203
+ inline_storages[name] = inline_storage unless inline_storages.key?(name)
204
+
205
+ inline_storages[name]
206
+ end
207
+
208
+ def length
209
+ insns
210
+ .each
211
+ .inject(0) do |sum, insn|
212
+ case insn
213
+ when Integer, Label, Symbol
214
+ sum
215
+ else
216
+ sum + insn.length
217
+ end
218
+ end
219
+ end
220
+
221
+ def eval
222
+ raise "Unsupported platform" if ISEQ_LOAD.nil?
223
+ Fiddle.dlunwrap(ISEQ_LOAD.call(Fiddle.dlwrap(to_a), 0, nil)).eval
224
+ end
225
+
226
+ def to_a
227
+ versions = RUBY_VERSION.split(".").map(&:to_i)
228
+
229
+ # Dump all of the instructions into a flat list.
230
+ dumped =
231
+ insns.map do |insn|
232
+ case insn
233
+ when Integer, Symbol
234
+ insn
235
+ when Label
236
+ insn.name
237
+ else
238
+ insn.to_a(self)
239
+ end
240
+ end
241
+
242
+ dumped_options = argument_options.dup
243
+ dumped_options[:opt].map!(&:name) if dumped_options[:opt]
244
+
245
+ # Next, return the instruction sequence as an array.
246
+ [
247
+ MAGIC,
248
+ versions[0],
249
+ versions[1],
250
+ 1,
251
+ {
252
+ arg_size: argument_size,
253
+ local_size: local_table.size,
254
+ stack_max: stack.maximum_size,
255
+ node_id: -1,
256
+ node_ids: [-1] * insns.length
257
+ },
258
+ name,
259
+ "<compiled>",
260
+ "<compiled>",
261
+ location.start_line,
262
+ type,
263
+ local_table.names,
264
+ dumped_options,
265
+ catch_table.map(&:to_a),
266
+ dumped
267
+ ]
268
+ end
269
+
270
+ def disasm
271
+ disassembler = Disassembler.new
272
+ disassembler.enqueue(self)
273
+ disassembler.format!
274
+ end
275
+
276
+ # This method converts our linked list of instructions into a final array
277
+ # and performs any other compilation steps necessary.
278
+ def compile!
279
+ specialize_instructions! if options.specialized_instruction?
280
+
281
+ length = 0
282
+ insns.each do |insn|
283
+ case insn
284
+ when Integer, Symbol
285
+ # skip
286
+ when Label
287
+ insn.patch!(:"label_#{length}")
288
+ when DefineClass
289
+ insn.class_iseq.compile!
290
+ length += insn.length
291
+ when DefineMethod, DefineSMethod
292
+ insn.method_iseq.compile!
293
+ length += insn.length
294
+ when InvokeSuper, Send
295
+ insn.block_iseq.compile! if insn.block_iseq
296
+ length += insn.length
297
+ when Once
298
+ insn.iseq.compile!
299
+ length += insn.length
300
+ else
301
+ length += insn.length
302
+ end
303
+ end
304
+
305
+ @insns = insns.to_a
306
+ end
307
+
308
+ def specialize_instructions!
309
+ insns.each_node do |node, value|
310
+ case value
311
+ when NewArray
312
+ next unless node.next_node
313
+
314
+ next_node = node.next_node
315
+ next unless next_node.value.is_a?(Send)
316
+ next if next_node.value.block_iseq
317
+
318
+ calldata = next_node.value.calldata
319
+ next unless calldata.flags == CallData::CALL_ARGS_SIMPLE
320
+ next unless calldata.argc == 0
321
+
322
+ case calldata.method
323
+ when :max
324
+ node.value = OptNewArrayMax.new(value.number)
325
+ node.next_node = next_node.next_node
326
+ when :min
327
+ node.value = OptNewArrayMin.new(value.number)
328
+ node.next_node = next_node.next_node
329
+ end
330
+ when PutObject, PutString
331
+ next unless node.next_node
332
+ next if value.is_a?(PutObject) && !value.object.is_a?(String)
333
+
334
+ next_node = node.next_node
335
+ next unless next_node.value.is_a?(Send)
336
+ next if next_node.value.block_iseq
337
+
338
+ calldata = next_node.value.calldata
339
+ next unless calldata.flags == CallData::CALL_ARGS_SIMPLE
340
+ next unless calldata.argc == 0
341
+
342
+ case calldata.method
343
+ when :freeze
344
+ node.value = OptStrFreeze.new(value.object, calldata)
345
+ node.next_node = next_node.next_node
346
+ when :-@
347
+ node.value = OptStrUMinus.new(value.object, calldata)
348
+ node.next_node = next_node.next_node
349
+ end
350
+ when Send
351
+ calldata = value.calldata
352
+
353
+ if !value.block_iseq &&
354
+ !calldata.flag?(CallData::CALL_ARGS_BLOCKARG)
355
+ # Specialize the send instruction. If it doesn't have a block
356
+ # attached, then we will replace it with an opt_send_without_block
357
+ # and do further specializations based on the called method and
358
+ # the number of arguments.
359
+ node.value =
360
+ case [calldata.method, calldata.argc]
361
+ when [:length, 0]
362
+ OptLength.new(calldata)
363
+ when [:size, 0]
364
+ OptSize.new(calldata)
365
+ when [:empty?, 0]
366
+ OptEmptyP.new(calldata)
367
+ when [:nil?, 0]
368
+ OptNilP.new(calldata)
369
+ when [:succ, 0]
370
+ OptSucc.new(calldata)
371
+ when [:!, 0]
372
+ OptNot.new(calldata)
373
+ when [:+, 1]
374
+ OptPlus.new(calldata)
375
+ when [:-, 1]
376
+ OptMinus.new(calldata)
377
+ when [:*, 1]
378
+ OptMult.new(calldata)
379
+ when [:/, 1]
380
+ OptDiv.new(calldata)
381
+ when [:%, 1]
382
+ OptMod.new(calldata)
383
+ when [:==, 1]
384
+ OptEq.new(calldata)
385
+ when [:!=, 1]
386
+ OptNEq.new(YARV.calldata(:==, 1), calldata)
387
+ when [:=~, 1]
388
+ OptRegExpMatch2.new(calldata)
389
+ when [:<, 1]
390
+ OptLT.new(calldata)
391
+ when [:<=, 1]
392
+ OptLE.new(calldata)
393
+ when [:>, 1]
394
+ OptGT.new(calldata)
395
+ when [:>=, 1]
396
+ OptGE.new(calldata)
397
+ when [:<<, 1]
398
+ OptLTLT.new(calldata)
399
+ when [:[], 1]
400
+ OptAref.new(calldata)
401
+ when [:&, 1]
402
+ OptAnd.new(calldata)
403
+ when [:|, 1]
404
+ OptOr.new(calldata)
405
+ when [:[]=, 2]
406
+ OptAset.new(calldata)
407
+ else
408
+ OptSendWithoutBlock.new(calldata)
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ ##########################################################################
416
+ # Child instruction sequence methods
417
+ ##########################################################################
418
+
419
+ def child_iseq(type, name, location)
420
+ InstructionSequence.new(type, name, self, location, options)
421
+ end
422
+
423
+ def block_child_iseq(location)
424
+ current = self
425
+ current = current.parent_iseq while current.type == :block
426
+ child_iseq(:block, "block in #{current.name}", location)
427
+ end
428
+
429
+ def class_child_iseq(name, location)
430
+ child_iseq(:class, "<class:#{name}>", location)
431
+ end
432
+
433
+ def method_child_iseq(name, location)
434
+ child_iseq(:method, name, location)
435
+ end
436
+
437
+ def module_child_iseq(name, location)
438
+ child_iseq(:class, "<module:#{name}>", location)
439
+ end
440
+
441
+ def singleton_class_child_iseq(location)
442
+ child_iseq(:class, "singleton class", location)
443
+ end
444
+
445
+ ##########################################################################
446
+ # Catch table methods
447
+ ##########################################################################
448
+
449
+ class CatchEntry
450
+ attr_reader :iseq, :begin_label, :end_label, :exit_label
451
+
452
+ def initialize(iseq, begin_label, end_label, exit_label)
453
+ @iseq = iseq
454
+ @begin_label = begin_label
455
+ @end_label = end_label
456
+ @exit_label = exit_label
457
+ end
458
+ end
459
+
460
+ class CatchBreak < CatchEntry
461
+ def to_a
462
+ [:break, iseq.to_a, begin_label.name, end_label.name, exit_label.name]
463
+ end
464
+ end
465
+
466
+ class CatchNext < CatchEntry
467
+ def to_a
468
+ [:next, nil, begin_label.name, end_label.name, exit_label.name]
469
+ end
470
+ end
471
+
472
+ class CatchRedo < CatchEntry
473
+ def to_a
474
+ [:redo, nil, begin_label.name, end_label.name, exit_label.name]
475
+ end
476
+ end
477
+
478
+ class CatchRescue < CatchEntry
479
+ def to_a
480
+ [
481
+ :rescue,
482
+ iseq.to_a,
483
+ begin_label.name,
484
+ end_label.name,
485
+ exit_label.name
486
+ ]
487
+ end
488
+ end
489
+
490
+ class CatchRetry < CatchEntry
491
+ def to_a
492
+ [:retry, nil, begin_label.name, end_label.name, exit_label.name]
493
+ end
494
+ end
495
+
496
+ def catch_break(iseq, begin_label, end_label, exit_label)
497
+ catch_table << CatchBreak.new(iseq, begin_label, end_label, exit_label)
498
+ end
499
+
500
+ def catch_next(begin_label, end_label, exit_label)
501
+ catch_table << CatchNext.new(nil, begin_label, end_label, exit_label)
502
+ end
503
+
504
+ def catch_redo(begin_label, end_label, exit_label)
505
+ catch_table << CatchRedo.new(nil, begin_label, end_label, exit_label)
506
+ end
507
+
508
+ def catch_rescue(iseq, begin_label, end_label, exit_label)
509
+ catch_table << CatchRescue.new(iseq, begin_label, end_label, exit_label)
510
+ end
511
+
512
+ def catch_retry(begin_label, end_label, exit_label)
513
+ catch_table << CatchRetry.new(nil, begin_label, end_label, exit_label)
514
+ end
515
+
516
+ ##########################################################################
517
+ # Instruction push methods
518
+ ##########################################################################
519
+
520
+ def label
521
+ Label.new
522
+ end
523
+
524
+ def push(value)
525
+ node = insns.push(value)
526
+
527
+ case value
528
+ when Array, Integer, Symbol
529
+ value
530
+ when Label
531
+ value.node = node
532
+ value
533
+ else
534
+ stack.change_by(-value.pops + value.pushes)
535
+ value
536
+ end
537
+ end
538
+
539
+ def event(name)
540
+ push(name)
541
+ end
542
+
543
+ def adjuststack(number)
544
+ push(AdjustStack.new(number))
545
+ end
546
+
547
+ def anytostring
548
+ push(AnyToString.new)
549
+ end
550
+
551
+ def branchif(label)
552
+ push(BranchIf.new(label))
553
+ end
554
+
555
+ def branchnil(label)
556
+ push(BranchNil.new(label))
557
+ end
558
+
559
+ def branchunless(label)
560
+ push(BranchUnless.new(label))
561
+ end
562
+
563
+ def checkkeyword(keyword_bits_index, keyword_index)
564
+ push(CheckKeyword.new(keyword_bits_index, keyword_index))
565
+ end
566
+
567
+ def checkmatch(type)
568
+ push(CheckMatch.new(type))
569
+ end
570
+
571
+ def checktype(type)
572
+ push(CheckType.new(type))
573
+ end
574
+
575
+ def concatarray
576
+ push(ConcatArray.new)
577
+ end
578
+
579
+ def concatstrings(number)
580
+ push(ConcatStrings.new(number))
581
+ end
582
+
583
+ def defined(type, name, message)
584
+ push(Defined.new(type, name, message))
585
+ end
586
+
587
+ def defineclass(name, class_iseq, flags)
588
+ push(DefineClass.new(name, class_iseq, flags))
589
+ end
590
+
591
+ def definemethod(name, method_iseq)
592
+ push(DefineMethod.new(name, method_iseq))
593
+ end
594
+
595
+ def definesmethod(name, method_iseq)
596
+ push(DefineSMethod.new(name, method_iseq))
597
+ end
598
+
599
+ def dup
600
+ push(Dup.new)
601
+ end
602
+
603
+ def duparray(object)
604
+ push(DupArray.new(object))
605
+ end
606
+
607
+ def duphash(object)
608
+ push(DupHash.new(object))
609
+ end
610
+
611
+ def dupn(number)
612
+ push(DupN.new(number))
613
+ end
614
+
615
+ def expandarray(length, flags)
616
+ push(ExpandArray.new(length, flags))
617
+ end
618
+
619
+ def getblockparam(index, level)
620
+ push(GetBlockParam.new(index, level))
621
+ end
622
+
623
+ def getblockparamproxy(index, level)
624
+ push(GetBlockParamProxy.new(index, level))
625
+ end
626
+
627
+ def getclassvariable(name)
628
+ if RUBY_VERSION < "3.0"
629
+ push(Legacy::GetClassVariable.new(name))
630
+ else
631
+ push(GetClassVariable.new(name, inline_storage_for(name)))
632
+ end
633
+ end
634
+
635
+ def getconstant(name)
636
+ push(GetConstant.new(name))
637
+ end
638
+
639
+ def getglobal(name)
640
+ push(GetGlobal.new(name))
641
+ end
642
+
643
+ def getinstancevariable(name)
644
+ if RUBY_VERSION < "3.2"
645
+ push(GetInstanceVariable.new(name, inline_storage_for(name)))
646
+ else
647
+ push(GetInstanceVariable.new(name, inline_storage))
648
+ end
649
+ end
650
+
651
+ def getlocal(index, level)
652
+ if options.operands_unification?
653
+ # Specialize the getlocal instruction based on the level of the
654
+ # local variable. If it's 0 or 1, then there's a specialized
655
+ # instruction that will look at the current scope or the parent
656
+ # scope, respectively, and requires fewer operands.
657
+ case level
658
+ when 0
659
+ push(GetLocalWC0.new(index))
660
+ when 1
661
+ push(GetLocalWC1.new(index))
662
+ else
663
+ push(GetLocal.new(index, level))
664
+ end
665
+ else
666
+ push(GetLocal.new(index, level))
667
+ end
668
+ end
669
+
670
+ def getspecial(key, type)
671
+ push(GetSpecial.new(key, type))
672
+ end
673
+
674
+ def intern
675
+ push(Intern.new)
676
+ end
677
+
678
+ def invokeblock(calldata)
679
+ push(InvokeBlock.new(calldata))
680
+ end
681
+
682
+ def invokesuper(calldata, block_iseq)
683
+ push(InvokeSuper.new(calldata, block_iseq))
684
+ end
685
+
686
+ def jump(label)
687
+ push(Jump.new(label))
688
+ end
689
+
690
+ def leave
691
+ push(Leave.new)
692
+ end
693
+
694
+ def newarray(number)
695
+ push(NewArray.new(number))
696
+ end
697
+
698
+ def newarraykwsplat(number)
699
+ push(NewArrayKwSplat.new(number))
700
+ end
701
+
702
+ def newhash(number)
703
+ push(NewHash.new(number))
704
+ end
705
+
706
+ def newrange(exclude_end)
707
+ push(NewRange.new(exclude_end))
708
+ end
709
+
710
+ def nop
711
+ push(Nop.new)
712
+ end
713
+
714
+ def objtostring(calldata)
715
+ push(ObjToString.new(calldata))
716
+ end
717
+
718
+ def once(iseq, cache)
719
+ push(Once.new(iseq, cache))
720
+ end
721
+
722
+ def opt_aref_with(object, calldata)
723
+ push(OptArefWith.new(object, calldata))
724
+ end
725
+
726
+ def opt_aset_with(object, calldata)
727
+ push(OptAsetWith.new(object, calldata))
728
+ end
729
+
730
+ def opt_case_dispatch(case_dispatch_hash, else_label)
731
+ push(OptCaseDispatch.new(case_dispatch_hash, else_label))
732
+ end
733
+
734
+ def opt_getconstant_path(names)
735
+ if RUBY_VERSION < "3.2" || !options.inline_const_cache?
736
+ cache = nil
737
+ cache_filled_label = nil
738
+
739
+ if options.inline_const_cache?
740
+ cache = inline_storage
741
+ cache_filled_label = label
742
+ opt_getinlinecache(cache_filled_label, cache)
743
+
744
+ if names[0] == :""
745
+ names.shift
746
+ pop
747
+ putobject(Object)
748
+ end
749
+ elsif names[0] == :""
750
+ names.shift
751
+ putobject(Object)
752
+ else
753
+ putnil
754
+ end
755
+
756
+ names.each_with_index do |name, index|
757
+ putobject(index == 0)
758
+ getconstant(name)
759
+ end
760
+
761
+ if options.inline_const_cache?
762
+ opt_setinlinecache(cache)
763
+ push(cache_filled_label)
764
+ end
765
+ else
766
+ push(OptGetConstantPath.new(names))
767
+ end
768
+ end
769
+
770
+ def opt_getinlinecache(label, cache)
771
+ push(Legacy::OptGetInlineCache.new(label, cache))
772
+ end
773
+
774
+ def opt_setinlinecache(cache)
775
+ push(Legacy::OptSetInlineCache.new(cache))
776
+ end
777
+
778
+ def pop
779
+ push(Pop.new)
780
+ end
781
+
782
+ def putnil
783
+ push(PutNil.new)
784
+ end
785
+
786
+ def putobject(object)
787
+ if options.operands_unification?
788
+ # Specialize the putobject instruction based on the value of the
789
+ # object. If it's 0 or 1, then there's a specialized instruction
790
+ # that will push the object onto the stack and requires fewer
791
+ # operands.
792
+ if object.eql?(0)
793
+ push(PutObjectInt2Fix0.new)
794
+ elsif object.eql?(1)
795
+ push(PutObjectInt2Fix1.new)
796
+ else
797
+ push(PutObject.new(object))
798
+ end
799
+ else
800
+ push(PutObject.new(object))
801
+ end
802
+ end
803
+
804
+ def putself
805
+ push(PutSelf.new)
806
+ end
807
+
808
+ def putspecialobject(object)
809
+ push(PutSpecialObject.new(object))
810
+ end
811
+
812
+ def putstring(object)
813
+ push(PutString.new(object))
814
+ end
815
+
816
+ def send(calldata, block_iseq = nil)
817
+ push(Send.new(calldata, block_iseq))
818
+ end
819
+
820
+ def setblockparam(index, level)
821
+ push(SetBlockParam.new(index, level))
822
+ end
823
+
824
+ def setclassvariable(name)
825
+ if RUBY_VERSION < "3.0"
826
+ push(Legacy::SetClassVariable.new(name))
827
+ else
828
+ push(SetClassVariable.new(name, inline_storage_for(name)))
829
+ end
830
+ end
831
+
832
+ def setconstant(name)
833
+ push(SetConstant.new(name))
834
+ end
835
+
836
+ def setglobal(name)
837
+ push(SetGlobal.new(name))
838
+ end
839
+
840
+ def setinstancevariable(name)
841
+ if RUBY_VERSION < "3.2"
842
+ push(SetInstanceVariable.new(name, inline_storage_for(name)))
843
+ else
844
+ push(SetInstanceVariable.new(name, inline_storage))
845
+ end
846
+ end
847
+
848
+ def setlocal(index, level)
849
+ if options.operands_unification?
850
+ # Specialize the setlocal instruction based on the level of the
851
+ # local variable. If it's 0 or 1, then there's a specialized
852
+ # instruction that will write to the current scope or the parent
853
+ # scope, respectively, and requires fewer operands.
854
+ case level
855
+ when 0
856
+ push(SetLocalWC0.new(index))
857
+ when 1
858
+ push(SetLocalWC1.new(index))
859
+ else
860
+ push(SetLocal.new(index, level))
861
+ end
862
+ else
863
+ push(SetLocal.new(index, level))
864
+ end
865
+ end
866
+
867
+ def setn(number)
868
+ push(SetN.new(number))
869
+ end
870
+
871
+ def setspecial(key)
872
+ push(SetSpecial.new(key))
873
+ end
874
+
875
+ def splatarray(flag)
876
+ push(SplatArray.new(flag))
877
+ end
878
+
879
+ def swap
880
+ push(Swap.new)
881
+ end
882
+
883
+ def throw(type)
884
+ push(Throw.new(type))
885
+ end
886
+
887
+ def topn(number)
888
+ push(TopN.new(number))
889
+ end
890
+
891
+ def toregexp(options, length)
892
+ push(ToRegExp.new(options, length))
893
+ end
894
+
895
+ # This method will create a new instruction sequence from a serialized
896
+ # RubyVM::InstructionSequence object.
897
+ def self.from(source, options = Compiler::Options.new, parent_iseq = nil)
898
+ iseq = new(source[9], source[5], parent_iseq, Location.default, options)
899
+
900
+ # set up the labels object so that the labels are shared between the
901
+ # location in the instruction sequence and the instructions that
902
+ # reference them
903
+ labels = Hash.new { |hash, name| hash[name] = Label.new(name) }
904
+
905
+ # set up the correct argument size
906
+ iseq.argument_size = source[4][:arg_size]
907
+
908
+ # set up all of the locals
909
+ source[10].each { |local| iseq.local_table.plain(local) }
910
+
911
+ # set up the argument options
912
+ iseq.argument_options.merge!(source[11])
913
+ if iseq.argument_options[:opt]
914
+ iseq.argument_options[:opt].map! { |opt| labels[opt] }
915
+ end
916
+
917
+ # set up the catch table
918
+ source[12].each do |entry|
919
+ case entry[0]
920
+ when :break
921
+ iseq.catch_break(
922
+ from(entry[1]),
923
+ labels[entry[2]],
924
+ labels[entry[3]],
925
+ labels[entry[4]]
926
+ )
927
+ when :next
928
+ iseq.catch_next(
929
+ labels[entry[2]],
930
+ labels[entry[3]],
931
+ labels[entry[4]]
932
+ )
933
+ when :rescue
934
+ iseq.catch_rescue(
935
+ from(entry[1]),
936
+ labels[entry[2]],
937
+ labels[entry[3]],
938
+ labels[entry[4]]
939
+ )
940
+ when :redo
941
+ iseq.catch_redo(
942
+ labels[entry[2]],
943
+ labels[entry[3]],
944
+ labels[entry[4]]
945
+ )
946
+ when :retry
947
+ iseq.catch_retry(
948
+ labels[entry[2]],
949
+ labels[entry[3]],
950
+ labels[entry[4]]
951
+ )
952
+ else
953
+ raise "unknown catch type: #{entry[0]}"
954
+ end
955
+ end
956
+
957
+ # set up all of the instructions
958
+ source[13].each do |insn|
959
+ # add line numbers
960
+ if insn.is_a?(Integer)
961
+ iseq.push(insn)
962
+ next
963
+ end
964
+
965
+ # add events and labels
966
+ if insn.is_a?(Symbol)
967
+ if insn.start_with?("label_")
968
+ iseq.push(labels[insn])
969
+ else
970
+ iseq.push(insn)
971
+ end
972
+ next
973
+ end
974
+
975
+ # add instructions, mapped to our own instruction classes
976
+ type, *opnds = insn
977
+
978
+ case type
979
+ when :adjuststack
980
+ iseq.adjuststack(opnds[0])
981
+ when :anytostring
982
+ iseq.anytostring
983
+ when :branchif
984
+ iseq.branchif(labels[opnds[0]])
985
+ when :branchnil
986
+ iseq.branchnil(labels[opnds[0]])
987
+ when :branchunless
988
+ iseq.branchunless(labels[opnds[0]])
989
+ when :checkkeyword
990
+ iseq.checkkeyword(iseq.local_table.size - opnds[0] + 2, opnds[1])
991
+ when :checkmatch
992
+ iseq.checkmatch(opnds[0])
993
+ when :checktype
994
+ iseq.checktype(opnds[0])
995
+ when :concatarray
996
+ iseq.concatarray
997
+ when :concatstrings
998
+ iseq.concatstrings(opnds[0])
999
+ when :defineclass
1000
+ iseq.defineclass(opnds[0], from(opnds[1], options, iseq), opnds[2])
1001
+ when :defined
1002
+ iseq.defined(opnds[0], opnds[1], opnds[2])
1003
+ when :definemethod
1004
+ iseq.definemethod(opnds[0], from(opnds[1], options, iseq))
1005
+ when :definesmethod
1006
+ iseq.definesmethod(opnds[0], from(opnds[1], options, iseq))
1007
+ when :dup
1008
+ iseq.dup
1009
+ when :duparray
1010
+ iseq.duparray(opnds[0])
1011
+ when :duphash
1012
+ iseq.duphash(opnds[0])
1013
+ when :dupn
1014
+ iseq.dupn(opnds[0])
1015
+ when :expandarray
1016
+ iseq.expandarray(opnds[0], opnds[1])
1017
+ when :getblockparam, :getblockparamproxy, :getlocal, :getlocal_WC_0,
1018
+ :getlocal_WC_1, :setblockparam, :setlocal, :setlocal_WC_0,
1019
+ :setlocal_WC_1
1020
+ current = iseq
1021
+ level = 0
1022
+
1023
+ case type
1024
+ when :getlocal_WC_1, :setlocal_WC_1
1025
+ level = 1
1026
+ when :getblockparam, :getblockparamproxy, :getlocal, :setblockparam,
1027
+ :setlocal
1028
+ level = opnds[1]
1029
+ end
1030
+
1031
+ level.times { current = current.parent_iseq }
1032
+ index = current.local_table.size - opnds[0] + 2
1033
+
1034
+ case type
1035
+ when :getblockparam
1036
+ iseq.getblockparam(index, level)
1037
+ when :getblockparamproxy
1038
+ iseq.getblockparamproxy(index, level)
1039
+ when :getlocal, :getlocal_WC_0, :getlocal_WC_1
1040
+ iseq.getlocal(index, level)
1041
+ when :setblockparam
1042
+ iseq.setblockparam(index, level)
1043
+ when :setlocal, :setlocal_WC_0, :setlocal_WC_1
1044
+ iseq.setlocal(index, level)
1045
+ end
1046
+ when :getclassvariable
1047
+ iseq.push(GetClassVariable.new(opnds[0], opnds[1]))
1048
+ when :getconstant
1049
+ iseq.getconstant(opnds[0])
1050
+ when :getglobal
1051
+ iseq.getglobal(opnds[0])
1052
+ when :getinstancevariable
1053
+ iseq.push(GetInstanceVariable.new(opnds[0], opnds[1]))
1054
+ when :getspecial
1055
+ iseq.getspecial(opnds[0], opnds[1])
1056
+ when :intern
1057
+ iseq.intern
1058
+ when :invokeblock
1059
+ iseq.invokeblock(CallData.from(opnds[0]))
1060
+ when :invokesuper
1061
+ block_iseq = opnds[1] ? from(opnds[1], options, iseq) : nil
1062
+ iseq.invokesuper(CallData.from(opnds[0]), block_iseq)
1063
+ when :jump
1064
+ iseq.jump(labels[opnds[0]])
1065
+ when :leave
1066
+ iseq.leave
1067
+ when :newarray
1068
+ iseq.newarray(opnds[0])
1069
+ when :newarraykwsplat
1070
+ iseq.newarraykwsplat(opnds[0])
1071
+ when :newhash
1072
+ iseq.newhash(opnds[0])
1073
+ when :newrange
1074
+ iseq.newrange(opnds[0])
1075
+ when :nop
1076
+ iseq.nop
1077
+ when :objtostring
1078
+ iseq.objtostring(CallData.from(opnds[0]))
1079
+ when :once
1080
+ iseq.once(from(opnds[0], options, iseq), opnds[1])
1081
+ when :opt_and, :opt_aref, :opt_aset, :opt_div, :opt_empty_p, :opt_eq,
1082
+ :opt_ge, :opt_gt, :opt_le, :opt_length, :opt_lt, :opt_ltlt,
1083
+ :opt_minus, :opt_mod, :opt_mult, :opt_nil_p, :opt_not, :opt_or,
1084
+ :opt_plus, :opt_regexpmatch2, :opt_send_without_block, :opt_size,
1085
+ :opt_succ
1086
+ iseq.send(CallData.from(opnds[0]), nil)
1087
+ when :opt_aref_with
1088
+ iseq.opt_aref_with(opnds[0], CallData.from(opnds[1]))
1089
+ when :opt_aset_with
1090
+ iseq.opt_aset_with(opnds[0], CallData.from(opnds[1]))
1091
+ when :opt_case_dispatch
1092
+ hash =
1093
+ opnds[0]
1094
+ .each_slice(2)
1095
+ .to_h
1096
+ .transform_values { |value| labels[value] }
1097
+ iseq.opt_case_dispatch(hash, labels[opnds[1]])
1098
+ when :opt_getconstant_path
1099
+ iseq.opt_getconstant_path(opnds[0])
1100
+ when :opt_getinlinecache
1101
+ iseq.opt_getinlinecache(labels[opnds[0]], opnds[1])
1102
+ when :opt_newarray_max
1103
+ iseq.newarray(opnds[0])
1104
+ iseq.send(YARV.calldata(:max))
1105
+ when :opt_newarray_min
1106
+ iseq.newarray(opnds[0])
1107
+ iseq.send(YARV.calldata(:min))
1108
+ when :opt_neq
1109
+ iseq.push(
1110
+ OptNEq.new(CallData.from(opnds[0]), CallData.from(opnds[1]))
1111
+ )
1112
+ when :opt_setinlinecache
1113
+ iseq.opt_setinlinecache(opnds[0])
1114
+ when :opt_str_freeze
1115
+ iseq.putstring(opnds[0])
1116
+ iseq.send(YARV.calldata(:freeze))
1117
+ when :opt_str_uminus
1118
+ iseq.putstring(opnds[0])
1119
+ iseq.send(YARV.calldata(:-@))
1120
+ when :pop
1121
+ iseq.pop
1122
+ when :putnil
1123
+ iseq.putnil
1124
+ when :putobject
1125
+ iseq.putobject(opnds[0])
1126
+ when :putobject_INT2FIX_0_
1127
+ iseq.putobject(0)
1128
+ when :putobject_INT2FIX_1_
1129
+ iseq.putobject(1)
1130
+ when :putself
1131
+ iseq.putself
1132
+ when :putstring
1133
+ iseq.putstring(opnds[0])
1134
+ when :putspecialobject
1135
+ iseq.putspecialobject(opnds[0])
1136
+ when :send
1137
+ block_iseq = opnds[1] ? from(opnds[1], options, iseq) : nil
1138
+ iseq.send(CallData.from(opnds[0]), block_iseq)
1139
+ when :setclassvariable
1140
+ iseq.push(SetClassVariable.new(opnds[0], opnds[1]))
1141
+ when :setconstant
1142
+ iseq.setconstant(opnds[0])
1143
+ when :setglobal
1144
+ iseq.setglobal(opnds[0])
1145
+ when :setinstancevariable
1146
+ iseq.push(SetInstanceVariable.new(opnds[0], opnds[1]))
1147
+ when :setn
1148
+ iseq.setn(opnds[0])
1149
+ when :setspecial
1150
+ iseq.setspecial(opnds[0])
1151
+ when :splatarray
1152
+ iseq.splatarray(opnds[0])
1153
+ when :swap
1154
+ iseq.swap
1155
+ when :throw
1156
+ iseq.throw(opnds[0])
1157
+ when :topn
1158
+ iseq.topn(opnds[0])
1159
+ when :toregexp
1160
+ iseq.toregexp(opnds[0], opnds[1])
1161
+ else
1162
+ raise "Unknown instruction type: #{type}"
1163
+ end
1164
+ end
1165
+
1166
+ iseq.compile! if iseq.type == :top
1167
+ iseq
1168
+ end
1169
+ end
1170
+ end
1171
+ end