syntax_tree 5.0.1 → 5.2.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,1275 @@
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 name of the instruction sequence.
120
+ attr_reader :name
121
+
122
+ # The source location of the instruction sequence.
123
+ attr_reader :file, :line
124
+
125
+ # The type of the instruction sequence.
126
+ attr_reader :type
127
+
128
+ # The parent instruction sequence, if there is one.
129
+ attr_reader :parent_iseq
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
+ name,
161
+ file,
162
+ line,
163
+ type,
164
+ parent_iseq = nil,
165
+ options = Compiler::Options.new
166
+ )
167
+ @name = name
168
+ @file = file
169
+ @line = line
170
+ @type = type
171
+ @parent_iseq = parent_iseq
172
+
173
+ @argument_size = 0
174
+ @argument_options = {}
175
+ @catch_table = []
176
+
177
+ @local_table = LocalTable.new
178
+ @inline_storages = {}
179
+ @insns = InstructionList.new
180
+ @storage_index = 0
181
+ @stack = Stack.new
182
+
183
+ @options = options
184
+ end
185
+
186
+ ##########################################################################
187
+ # Query methods
188
+ ##########################################################################
189
+
190
+ def local_variable(name, level = 0)
191
+ if (lookup = local_table.find(name, level))
192
+ lookup
193
+ elsif parent_iseq
194
+ parent_iseq.local_variable(name, level + 1)
195
+ end
196
+ end
197
+
198
+ def inline_storage
199
+ storage = storage_index
200
+ @storage_index += 1
201
+ storage
202
+ end
203
+
204
+ def inline_storage_for(name)
205
+ inline_storages[name] = inline_storage unless inline_storages.key?(name)
206
+
207
+ inline_storages[name]
208
+ end
209
+
210
+ def length
211
+ insns
212
+ .each
213
+ .inject(0) do |sum, insn|
214
+ case insn
215
+ when Integer, Label, Symbol
216
+ sum
217
+ else
218
+ sum + insn.length
219
+ end
220
+ end
221
+ end
222
+
223
+ def eval
224
+ raise "Unsupported platform" if ISEQ_LOAD.nil?
225
+ Fiddle.dlunwrap(ISEQ_LOAD.call(Fiddle.dlwrap(to_a), 0, nil)).eval
226
+ end
227
+
228
+ def to_a
229
+ versions = RUBY_VERSION.split(".").map(&:to_i)
230
+
231
+ # Dump all of the instructions into a flat list.
232
+ dumped =
233
+ insns.map do |insn|
234
+ case insn
235
+ when Integer, Symbol
236
+ insn
237
+ when Label
238
+ insn.name
239
+ else
240
+ insn.to_a(self)
241
+ end
242
+ end
243
+
244
+ dumped_options = argument_options.dup
245
+ dumped_options[:opt].map!(&:name) if dumped_options[:opt]
246
+
247
+ # Next, return the instruction sequence as an array.
248
+ [
249
+ MAGIC,
250
+ versions[0],
251
+ versions[1],
252
+ 1,
253
+ {
254
+ arg_size: argument_size,
255
+ local_size: local_table.size,
256
+ stack_max: stack.maximum_size,
257
+ node_id: -1,
258
+ node_ids: [-1] * insns.length
259
+ },
260
+ name,
261
+ file,
262
+ "<compiled>",
263
+ line,
264
+ type,
265
+ local_table.names,
266
+ dumped_options,
267
+ catch_table.map(&:to_a),
268
+ dumped
269
+ ]
270
+ end
271
+
272
+ def disasm
273
+ disassembler = Disassembler.new
274
+ disassembler.enqueue(self)
275
+ disassembler.format!
276
+ end
277
+
278
+ # This method converts our linked list of instructions into a final array
279
+ # and performs any other compilation steps necessary.
280
+ def compile!
281
+ specialize_instructions! if options.specialized_instruction?
282
+
283
+ catch_table.each do |catch_entry|
284
+ if !catch_entry.is_a?(CatchBreak) && catch_entry.iseq
285
+ catch_entry.iseq.compile!
286
+ end
287
+ end
288
+
289
+ length = 0
290
+ insns.each do |insn|
291
+ case insn
292
+ when Integer, Symbol
293
+ # skip
294
+ when Label
295
+ insn.patch!(:"label_#{length}")
296
+ when DefineClass
297
+ insn.class_iseq.compile!
298
+ length += insn.length
299
+ when DefineMethod, DefineSMethod
300
+ insn.method_iseq.compile!
301
+ length += insn.length
302
+ when InvokeSuper, Send
303
+ insn.block_iseq.compile! if insn.block_iseq
304
+ length += insn.length
305
+ when Once
306
+ insn.iseq.compile!
307
+ length += insn.length
308
+ else
309
+ length += insn.length
310
+ end
311
+ end
312
+
313
+ @insns = insns.to_a
314
+ end
315
+
316
+ def specialize_instructions!
317
+ insns.each_node do |node, value|
318
+ case value
319
+ when NewArray
320
+ next unless node.next_node
321
+
322
+ next_node = node.next_node
323
+ next unless next_node.value.is_a?(Send)
324
+ next if next_node.value.block_iseq
325
+
326
+ calldata = next_node.value.calldata
327
+ next unless calldata.flags == CallData::CALL_ARGS_SIMPLE
328
+ next unless calldata.argc == 0
329
+
330
+ case calldata.method
331
+ when :max
332
+ node.value = OptNewArrayMax.new(value.number)
333
+ node.next_node = next_node.next_node
334
+ when :min
335
+ node.value = OptNewArrayMin.new(value.number)
336
+ node.next_node = next_node.next_node
337
+ end
338
+ when PutObject, PutString
339
+ next unless node.next_node
340
+ next if value.is_a?(PutObject) && !value.object.is_a?(String)
341
+
342
+ next_node = node.next_node
343
+ next unless next_node.value.is_a?(Send)
344
+ next if next_node.value.block_iseq
345
+
346
+ calldata = next_node.value.calldata
347
+ next unless calldata.flags == CallData::CALL_ARGS_SIMPLE
348
+ next unless calldata.argc == 0
349
+
350
+ case calldata.method
351
+ when :freeze
352
+ node.value = OptStrFreeze.new(value.object, calldata)
353
+ node.next_node = next_node.next_node
354
+ when :-@
355
+ node.value = OptStrUMinus.new(value.object, calldata)
356
+ node.next_node = next_node.next_node
357
+ end
358
+ when Send
359
+ calldata = value.calldata
360
+
361
+ if !value.block_iseq &&
362
+ !calldata.flag?(CallData::CALL_ARGS_BLOCKARG)
363
+ # Specialize the send instruction. If it doesn't have a block
364
+ # attached, then we will replace it with an opt_send_without_block
365
+ # and do further specializations based on the called method and
366
+ # the number of arguments.
367
+ node.value =
368
+ case [calldata.method, calldata.argc]
369
+ when [:length, 0]
370
+ OptLength.new(calldata)
371
+ when [:size, 0]
372
+ OptSize.new(calldata)
373
+ when [:empty?, 0]
374
+ OptEmptyP.new(calldata)
375
+ when [:nil?, 0]
376
+ OptNilP.new(calldata)
377
+ when [:succ, 0]
378
+ OptSucc.new(calldata)
379
+ when [:!, 0]
380
+ OptNot.new(calldata)
381
+ when [:+, 1]
382
+ OptPlus.new(calldata)
383
+ when [:-, 1]
384
+ OptMinus.new(calldata)
385
+ when [:*, 1]
386
+ OptMult.new(calldata)
387
+ when [:/, 1]
388
+ OptDiv.new(calldata)
389
+ when [:%, 1]
390
+ OptMod.new(calldata)
391
+ when [:==, 1]
392
+ OptEq.new(calldata)
393
+ when [:!=, 1]
394
+ OptNEq.new(YARV.calldata(:==, 1), calldata)
395
+ when [:=~, 1]
396
+ OptRegExpMatch2.new(calldata)
397
+ when [:<, 1]
398
+ OptLT.new(calldata)
399
+ when [:<=, 1]
400
+ OptLE.new(calldata)
401
+ when [:>, 1]
402
+ OptGT.new(calldata)
403
+ when [:>=, 1]
404
+ OptGE.new(calldata)
405
+ when [:<<, 1]
406
+ OptLTLT.new(calldata)
407
+ when [:[], 1]
408
+ OptAref.new(calldata)
409
+ when [:&, 1]
410
+ OptAnd.new(calldata)
411
+ when [:|, 1]
412
+ OptOr.new(calldata)
413
+ when [:[]=, 2]
414
+ OptAset.new(calldata)
415
+ else
416
+ OptSendWithoutBlock.new(calldata)
417
+ end
418
+ end
419
+ end
420
+ end
421
+ end
422
+
423
+ ##########################################################################
424
+ # Child instruction sequence methods
425
+ ##########################################################################
426
+
427
+ def child_iseq(name, line, type)
428
+ InstructionSequence.new(name, file, line, type, self, options)
429
+ end
430
+
431
+ def block_child_iseq(line)
432
+ current = self
433
+ current = current.parent_iseq while current.type == :block
434
+ child_iseq("block in #{current.name}", line, :block)
435
+ end
436
+
437
+ def class_child_iseq(name, line)
438
+ child_iseq("<class:#{name}>", line, :class)
439
+ end
440
+
441
+ def method_child_iseq(name, line)
442
+ child_iseq(name, line, :method)
443
+ end
444
+
445
+ def module_child_iseq(name, line)
446
+ child_iseq("<module:#{name}>", line, :class)
447
+ end
448
+
449
+ def singleton_class_child_iseq(line)
450
+ child_iseq("singleton class", line, :class)
451
+ end
452
+
453
+ ##########################################################################
454
+ # Catch table methods
455
+ ##########################################################################
456
+
457
+ class CatchEntry
458
+ attr_reader :iseq, :begin_label, :end_label, :exit_label, :restore_sp
459
+
460
+ def initialize(iseq, begin_label, end_label, exit_label, restore_sp)
461
+ @iseq = iseq
462
+ @begin_label = begin_label
463
+ @end_label = end_label
464
+ @exit_label = exit_label
465
+ @restore_sp = restore_sp
466
+ end
467
+ end
468
+
469
+ class CatchBreak < CatchEntry
470
+ def to_a
471
+ [
472
+ :break,
473
+ iseq.to_a,
474
+ begin_label.name,
475
+ end_label.name,
476
+ exit_label.name,
477
+ restore_sp
478
+ ]
479
+ end
480
+ end
481
+
482
+ class CatchEnsure < CatchEntry
483
+ def to_a
484
+ [
485
+ :ensure,
486
+ iseq.to_a,
487
+ begin_label.name,
488
+ end_label.name,
489
+ exit_label.name
490
+ ]
491
+ end
492
+ end
493
+
494
+ class CatchNext < CatchEntry
495
+ def to_a
496
+ [:next, nil, begin_label.name, end_label.name, exit_label.name]
497
+ end
498
+ end
499
+
500
+ class CatchRedo < CatchEntry
501
+ def to_a
502
+ [:redo, nil, begin_label.name, end_label.name, exit_label.name]
503
+ end
504
+ end
505
+
506
+ class CatchRescue < CatchEntry
507
+ def to_a
508
+ [
509
+ :rescue,
510
+ iseq.to_a,
511
+ begin_label.name,
512
+ end_label.name,
513
+ exit_label.name
514
+ ]
515
+ end
516
+ end
517
+
518
+ class CatchRetry < CatchEntry
519
+ def to_a
520
+ [:retry, nil, begin_label.name, end_label.name, exit_label.name]
521
+ end
522
+ end
523
+
524
+ def catch_break(iseq, begin_label, end_label, exit_label, restore_sp)
525
+ catch_table << CatchBreak.new(
526
+ iseq,
527
+ begin_label,
528
+ end_label,
529
+ exit_label,
530
+ restore_sp
531
+ )
532
+ end
533
+
534
+ def catch_ensure(iseq, begin_label, end_label, exit_label, restore_sp)
535
+ catch_table << CatchEnsure.new(
536
+ iseq,
537
+ begin_label,
538
+ end_label,
539
+ exit_label,
540
+ restore_sp
541
+ )
542
+ end
543
+
544
+ def catch_next(begin_label, end_label, exit_label, restore_sp)
545
+ catch_table << CatchNext.new(
546
+ nil,
547
+ begin_label,
548
+ end_label,
549
+ exit_label,
550
+ restore_sp
551
+ )
552
+ end
553
+
554
+ def catch_redo(begin_label, end_label, exit_label, restore_sp)
555
+ catch_table << CatchRedo.new(
556
+ nil,
557
+ begin_label,
558
+ end_label,
559
+ exit_label,
560
+ restore_sp
561
+ )
562
+ end
563
+
564
+ def catch_rescue(iseq, begin_label, end_label, exit_label, restore_sp)
565
+ catch_table << CatchRescue.new(
566
+ iseq,
567
+ begin_label,
568
+ end_label,
569
+ exit_label,
570
+ restore_sp
571
+ )
572
+ end
573
+
574
+ def catch_retry(begin_label, end_label, exit_label, restore_sp)
575
+ catch_table << CatchRetry.new(
576
+ nil,
577
+ begin_label,
578
+ end_label,
579
+ exit_label,
580
+ restore_sp
581
+ )
582
+ end
583
+
584
+ ##########################################################################
585
+ # Instruction push methods
586
+ ##########################################################################
587
+
588
+ def label
589
+ Label.new
590
+ end
591
+
592
+ def push(value)
593
+ node = insns.push(value)
594
+
595
+ case value
596
+ when Array, Integer, Symbol
597
+ value
598
+ when Label
599
+ value.node = node
600
+ value
601
+ else
602
+ stack.change_by(-value.pops + value.pushes)
603
+ value
604
+ end
605
+ end
606
+
607
+ def event(name)
608
+ push(name)
609
+ end
610
+
611
+ def adjuststack(number)
612
+ push(AdjustStack.new(number))
613
+ end
614
+
615
+ def anytostring
616
+ push(AnyToString.new)
617
+ end
618
+
619
+ def branchif(label)
620
+ push(BranchIf.new(label))
621
+ end
622
+
623
+ def branchnil(label)
624
+ push(BranchNil.new(label))
625
+ end
626
+
627
+ def branchunless(label)
628
+ push(BranchUnless.new(label))
629
+ end
630
+
631
+ def checkkeyword(keyword_bits_index, keyword_index)
632
+ push(CheckKeyword.new(keyword_bits_index, keyword_index))
633
+ end
634
+
635
+ def checkmatch(type)
636
+ push(CheckMatch.new(type))
637
+ end
638
+
639
+ def checktype(type)
640
+ push(CheckType.new(type))
641
+ end
642
+
643
+ def concatarray
644
+ push(ConcatArray.new)
645
+ end
646
+
647
+ def concatstrings(number)
648
+ push(ConcatStrings.new(number))
649
+ end
650
+
651
+ def defined(type, name, message)
652
+ push(Defined.new(type, name, message))
653
+ end
654
+
655
+ def defineclass(name, class_iseq, flags)
656
+ push(DefineClass.new(name, class_iseq, flags))
657
+ end
658
+
659
+ def definemethod(name, method_iseq)
660
+ push(DefineMethod.new(name, method_iseq))
661
+ end
662
+
663
+ def definesmethod(name, method_iseq)
664
+ push(DefineSMethod.new(name, method_iseq))
665
+ end
666
+
667
+ def dup
668
+ push(Dup.new)
669
+ end
670
+
671
+ def duparray(object)
672
+ push(DupArray.new(object))
673
+ end
674
+
675
+ def duphash(object)
676
+ push(DupHash.new(object))
677
+ end
678
+
679
+ def dupn(number)
680
+ push(DupN.new(number))
681
+ end
682
+
683
+ def expandarray(length, flags)
684
+ push(ExpandArray.new(length, flags))
685
+ end
686
+
687
+ def getblockparam(index, level)
688
+ push(GetBlockParam.new(index, level))
689
+ end
690
+
691
+ def getblockparamproxy(index, level)
692
+ push(GetBlockParamProxy.new(index, level))
693
+ end
694
+
695
+ def getclassvariable(name)
696
+ if RUBY_VERSION < "3.0"
697
+ push(Legacy::GetClassVariable.new(name))
698
+ else
699
+ push(GetClassVariable.new(name, inline_storage_for(name)))
700
+ end
701
+ end
702
+
703
+ def getconstant(name)
704
+ push(GetConstant.new(name))
705
+ end
706
+
707
+ def getglobal(name)
708
+ push(GetGlobal.new(name))
709
+ end
710
+
711
+ def getinstancevariable(name)
712
+ if RUBY_VERSION < "3.2"
713
+ push(GetInstanceVariable.new(name, inline_storage_for(name)))
714
+ else
715
+ push(GetInstanceVariable.new(name, inline_storage))
716
+ end
717
+ end
718
+
719
+ def getlocal(index, level)
720
+ if options.operands_unification?
721
+ # Specialize the getlocal instruction based on the level of the
722
+ # local variable. If it's 0 or 1, then there's a specialized
723
+ # instruction that will look at the current scope or the parent
724
+ # scope, respectively, and requires fewer operands.
725
+ case level
726
+ when 0
727
+ push(GetLocalWC0.new(index))
728
+ when 1
729
+ push(GetLocalWC1.new(index))
730
+ else
731
+ push(GetLocal.new(index, level))
732
+ end
733
+ else
734
+ push(GetLocal.new(index, level))
735
+ end
736
+ end
737
+
738
+ def getspecial(key, type)
739
+ push(GetSpecial.new(key, type))
740
+ end
741
+
742
+ def intern
743
+ push(Intern.new)
744
+ end
745
+
746
+ def invokeblock(calldata)
747
+ push(InvokeBlock.new(calldata))
748
+ end
749
+
750
+ def invokesuper(calldata, block_iseq)
751
+ push(InvokeSuper.new(calldata, block_iseq))
752
+ end
753
+
754
+ def jump(label)
755
+ push(Jump.new(label))
756
+ end
757
+
758
+ def leave
759
+ push(Leave.new)
760
+ end
761
+
762
+ def newarray(number)
763
+ push(NewArray.new(number))
764
+ end
765
+
766
+ def newarraykwsplat(number)
767
+ push(NewArrayKwSplat.new(number))
768
+ end
769
+
770
+ def newhash(number)
771
+ push(NewHash.new(number))
772
+ end
773
+
774
+ def newrange(exclude_end)
775
+ push(NewRange.new(exclude_end))
776
+ end
777
+
778
+ def nop
779
+ push(Nop.new)
780
+ end
781
+
782
+ def objtostring(calldata)
783
+ push(ObjToString.new(calldata))
784
+ end
785
+
786
+ def once(iseq, cache)
787
+ push(Once.new(iseq, cache))
788
+ end
789
+
790
+ def opt_aref_with(object, calldata)
791
+ push(OptArefWith.new(object, calldata))
792
+ end
793
+
794
+ def opt_aset_with(object, calldata)
795
+ push(OptAsetWith.new(object, calldata))
796
+ end
797
+
798
+ def opt_case_dispatch(case_dispatch_hash, else_label)
799
+ push(OptCaseDispatch.new(case_dispatch_hash, else_label))
800
+ end
801
+
802
+ def opt_getconstant_path(names)
803
+ if RUBY_VERSION < "3.2" || !options.inline_const_cache?
804
+ cache = nil
805
+ cache_filled_label = nil
806
+
807
+ if options.inline_const_cache?
808
+ cache = inline_storage
809
+ cache_filled_label = label
810
+ opt_getinlinecache(cache_filled_label, cache)
811
+
812
+ if names[0] == :""
813
+ names.shift
814
+ pop
815
+ putobject(Object)
816
+ end
817
+ elsif names[0] == :""
818
+ names.shift
819
+ putobject(Object)
820
+ else
821
+ putnil
822
+ end
823
+
824
+ names.each_with_index do |name, index|
825
+ putobject(index == 0)
826
+ getconstant(name)
827
+ end
828
+
829
+ if options.inline_const_cache?
830
+ opt_setinlinecache(cache)
831
+ push(cache_filled_label)
832
+ end
833
+ else
834
+ push(OptGetConstantPath.new(names))
835
+ end
836
+ end
837
+
838
+ def opt_getinlinecache(label, cache)
839
+ push(Legacy::OptGetInlineCache.new(label, cache))
840
+ end
841
+
842
+ def opt_setinlinecache(cache)
843
+ push(Legacy::OptSetInlineCache.new(cache))
844
+ end
845
+
846
+ def pop
847
+ push(Pop.new)
848
+ end
849
+
850
+ def putnil
851
+ push(PutNil.new)
852
+ end
853
+
854
+ def putobject(object)
855
+ if options.operands_unification?
856
+ # Specialize the putobject instruction based on the value of the
857
+ # object. If it's 0 or 1, then there's a specialized instruction
858
+ # that will push the object onto the stack and requires fewer
859
+ # operands.
860
+ if object.eql?(0)
861
+ push(PutObjectInt2Fix0.new)
862
+ elsif object.eql?(1)
863
+ push(PutObjectInt2Fix1.new)
864
+ else
865
+ push(PutObject.new(object))
866
+ end
867
+ else
868
+ push(PutObject.new(object))
869
+ end
870
+ end
871
+
872
+ def putself
873
+ push(PutSelf.new)
874
+ end
875
+
876
+ def putspecialobject(object)
877
+ push(PutSpecialObject.new(object))
878
+ end
879
+
880
+ def putstring(object)
881
+ push(PutString.new(object))
882
+ end
883
+
884
+ def send(calldata, block_iseq = nil)
885
+ push(Send.new(calldata, block_iseq))
886
+ end
887
+
888
+ def setblockparam(index, level)
889
+ push(SetBlockParam.new(index, level))
890
+ end
891
+
892
+ def setclassvariable(name)
893
+ if RUBY_VERSION < "3.0"
894
+ push(Legacy::SetClassVariable.new(name))
895
+ else
896
+ push(SetClassVariable.new(name, inline_storage_for(name)))
897
+ end
898
+ end
899
+
900
+ def setconstant(name)
901
+ push(SetConstant.new(name))
902
+ end
903
+
904
+ def setglobal(name)
905
+ push(SetGlobal.new(name))
906
+ end
907
+
908
+ def setinstancevariable(name)
909
+ if RUBY_VERSION < "3.2"
910
+ push(SetInstanceVariable.new(name, inline_storage_for(name)))
911
+ else
912
+ push(SetInstanceVariable.new(name, inline_storage))
913
+ end
914
+ end
915
+
916
+ def setlocal(index, level)
917
+ if options.operands_unification?
918
+ # Specialize the setlocal instruction based on the level of the
919
+ # local variable. If it's 0 or 1, then there's a specialized
920
+ # instruction that will write to the current scope or the parent
921
+ # scope, respectively, and requires fewer operands.
922
+ case level
923
+ when 0
924
+ push(SetLocalWC0.new(index))
925
+ when 1
926
+ push(SetLocalWC1.new(index))
927
+ else
928
+ push(SetLocal.new(index, level))
929
+ end
930
+ else
931
+ push(SetLocal.new(index, level))
932
+ end
933
+ end
934
+
935
+ def setn(number)
936
+ push(SetN.new(number))
937
+ end
938
+
939
+ def setspecial(key)
940
+ push(SetSpecial.new(key))
941
+ end
942
+
943
+ def splatarray(flag)
944
+ push(SplatArray.new(flag))
945
+ end
946
+
947
+ def swap
948
+ push(Swap.new)
949
+ end
950
+
951
+ def throw(type)
952
+ push(Throw.new(type))
953
+ end
954
+
955
+ def topn(number)
956
+ push(TopN.new(number))
957
+ end
958
+
959
+ def toregexp(options, length)
960
+ push(ToRegExp.new(options, length))
961
+ end
962
+
963
+ # This method will create a new instruction sequence from a serialized
964
+ # RubyVM::InstructionSequence object.
965
+ def self.from(source, options = Compiler::Options.new, parent_iseq = nil)
966
+ iseq =
967
+ new(source[5], source[6], source[8], source[9], parent_iseq, options)
968
+
969
+ # set up the labels object so that the labels are shared between the
970
+ # location in the instruction sequence and the instructions that
971
+ # reference them
972
+ labels = Hash.new { |hash, name| hash[name] = Label.new(name) }
973
+
974
+ # set up the correct argument size
975
+ iseq.argument_size = source[4][:arg_size]
976
+
977
+ # set up all of the locals
978
+ source[10].each { |local| iseq.local_table.plain(local) }
979
+
980
+ # set up the argument options
981
+ iseq.argument_options.merge!(source[11])
982
+ if iseq.argument_options[:opt]
983
+ iseq.argument_options[:opt].map! { |opt| labels[opt] }
984
+ end
985
+
986
+ # track the child block iseqs so that our catch table can point to the
987
+ # correctly created iseqs
988
+ block_iseqs = []
989
+
990
+ # set up all of the instructions
991
+ source[13].each do |insn|
992
+ # add line numbers
993
+ if insn.is_a?(Integer)
994
+ iseq.push(insn)
995
+ next
996
+ end
997
+
998
+ # add events and labels
999
+ if insn.is_a?(Symbol)
1000
+ if insn.start_with?("label_")
1001
+ iseq.push(labels[insn])
1002
+ else
1003
+ iseq.push(insn)
1004
+ end
1005
+ next
1006
+ end
1007
+
1008
+ # add instructions, mapped to our own instruction classes
1009
+ type, *opnds = insn
1010
+
1011
+ case type
1012
+ when :adjuststack
1013
+ iseq.adjuststack(opnds[0])
1014
+ when :anytostring
1015
+ iseq.anytostring
1016
+ when :branchif
1017
+ iseq.branchif(labels[opnds[0]])
1018
+ when :branchnil
1019
+ iseq.branchnil(labels[opnds[0]])
1020
+ when :branchunless
1021
+ iseq.branchunless(labels[opnds[0]])
1022
+ when :checkkeyword
1023
+ iseq.checkkeyword(iseq.local_table.size - opnds[0] + 2, opnds[1])
1024
+ when :checkmatch
1025
+ iseq.checkmatch(opnds[0])
1026
+ when :checktype
1027
+ iseq.checktype(opnds[0])
1028
+ when :concatarray
1029
+ iseq.concatarray
1030
+ when :concatstrings
1031
+ iseq.concatstrings(opnds[0])
1032
+ when :defineclass
1033
+ iseq.defineclass(opnds[0], from(opnds[1], options, iseq), opnds[2])
1034
+ when :defined
1035
+ iseq.defined(opnds[0], opnds[1], opnds[2])
1036
+ when :definemethod
1037
+ iseq.definemethod(opnds[0], from(opnds[1], options, iseq))
1038
+ when :definesmethod
1039
+ iseq.definesmethod(opnds[0], from(opnds[1], options, iseq))
1040
+ when :dup
1041
+ iseq.dup
1042
+ when :duparray
1043
+ iseq.duparray(opnds[0])
1044
+ when :duphash
1045
+ iseq.duphash(opnds[0])
1046
+ when :dupn
1047
+ iseq.dupn(opnds[0])
1048
+ when :expandarray
1049
+ iseq.expandarray(opnds[0], opnds[1])
1050
+ when :getblockparam, :getblockparamproxy, :getlocal, :getlocal_WC_0,
1051
+ :getlocal_WC_1, :setblockparam, :setlocal, :setlocal_WC_0,
1052
+ :setlocal_WC_1
1053
+ current = iseq
1054
+ level = 0
1055
+
1056
+ case type
1057
+ when :getlocal_WC_1, :setlocal_WC_1
1058
+ level = 1
1059
+ when :getblockparam, :getblockparamproxy, :getlocal, :setblockparam,
1060
+ :setlocal
1061
+ level = opnds[1]
1062
+ end
1063
+
1064
+ level.times { current = current.parent_iseq }
1065
+ index = current.local_table.size - opnds[0] + 2
1066
+
1067
+ case type
1068
+ when :getblockparam
1069
+ iseq.getblockparam(index, level)
1070
+ when :getblockparamproxy
1071
+ iseq.getblockparamproxy(index, level)
1072
+ when :getlocal, :getlocal_WC_0, :getlocal_WC_1
1073
+ iseq.getlocal(index, level)
1074
+ when :setblockparam
1075
+ iseq.setblockparam(index, level)
1076
+ when :setlocal, :setlocal_WC_0, :setlocal_WC_1
1077
+ iseq.setlocal(index, level)
1078
+ end
1079
+ when :getclassvariable
1080
+ iseq.push(GetClassVariable.new(opnds[0], opnds[1]))
1081
+ when :getconstant
1082
+ iseq.getconstant(opnds[0])
1083
+ when :getglobal
1084
+ iseq.getglobal(opnds[0])
1085
+ when :getinstancevariable
1086
+ iseq.push(GetInstanceVariable.new(opnds[0], opnds[1]))
1087
+ when :getspecial
1088
+ iseq.getspecial(opnds[0], opnds[1])
1089
+ when :intern
1090
+ iseq.intern
1091
+ when :invokeblock
1092
+ iseq.invokeblock(CallData.from(opnds[0]))
1093
+ when :invokesuper
1094
+ block_iseq = opnds[1] ? from(opnds[1], options, iseq) : nil
1095
+ iseq.invokesuper(CallData.from(opnds[0]), block_iseq)
1096
+ when :jump
1097
+ iseq.jump(labels[opnds[0]])
1098
+ when :leave
1099
+ iseq.leave
1100
+ when :newarray
1101
+ iseq.newarray(opnds[0])
1102
+ when :newarraykwsplat
1103
+ iseq.newarraykwsplat(opnds[0])
1104
+ when :newhash
1105
+ iseq.newhash(opnds[0])
1106
+ when :newrange
1107
+ iseq.newrange(opnds[0])
1108
+ when :nop
1109
+ iseq.nop
1110
+ when :objtostring
1111
+ iseq.objtostring(CallData.from(opnds[0]))
1112
+ when :once
1113
+ iseq.once(from(opnds[0], options, iseq), opnds[1])
1114
+ when :opt_and, :opt_aref, :opt_aset, :opt_div, :opt_empty_p, :opt_eq,
1115
+ :opt_ge, :opt_gt, :opt_le, :opt_length, :opt_lt, :opt_ltlt,
1116
+ :opt_minus, :opt_mod, :opt_mult, :opt_nil_p, :opt_not, :opt_or,
1117
+ :opt_plus, :opt_regexpmatch2, :opt_send_without_block, :opt_size,
1118
+ :opt_succ
1119
+ iseq.send(CallData.from(opnds[0]), nil)
1120
+ when :opt_aref_with
1121
+ iseq.opt_aref_with(opnds[0], CallData.from(opnds[1]))
1122
+ when :opt_aset_with
1123
+ iseq.opt_aset_with(opnds[0], CallData.from(opnds[1]))
1124
+ when :opt_case_dispatch
1125
+ hash =
1126
+ opnds[0]
1127
+ .each_slice(2)
1128
+ .to_h
1129
+ .transform_values { |value| labels[value] }
1130
+ iseq.opt_case_dispatch(hash, labels[opnds[1]])
1131
+ when :opt_getconstant_path
1132
+ iseq.opt_getconstant_path(opnds[0])
1133
+ when :opt_getinlinecache
1134
+ iseq.opt_getinlinecache(labels[opnds[0]], opnds[1])
1135
+ when :opt_newarray_max
1136
+ iseq.newarray(opnds[0])
1137
+ iseq.send(YARV.calldata(:max))
1138
+ when :opt_newarray_min
1139
+ iseq.newarray(opnds[0])
1140
+ iseq.send(YARV.calldata(:min))
1141
+ when :opt_neq
1142
+ iseq.push(
1143
+ OptNEq.new(CallData.from(opnds[0]), CallData.from(opnds[1]))
1144
+ )
1145
+ when :opt_setinlinecache
1146
+ iseq.opt_setinlinecache(opnds[0])
1147
+ when :opt_str_freeze
1148
+ iseq.putstring(opnds[0])
1149
+ iseq.send(YARV.calldata(:freeze))
1150
+ when :opt_str_uminus
1151
+ iseq.putstring(opnds[0])
1152
+ iseq.send(YARV.calldata(:-@))
1153
+ when :pop
1154
+ iseq.pop
1155
+ when :putnil
1156
+ iseq.putnil
1157
+ when :putobject
1158
+ iseq.putobject(opnds[0])
1159
+ when :putobject_INT2FIX_0_
1160
+ iseq.putobject(0)
1161
+ when :putobject_INT2FIX_1_
1162
+ iseq.putobject(1)
1163
+ when :putself
1164
+ iseq.putself
1165
+ when :putstring
1166
+ iseq.putstring(opnds[0])
1167
+ when :putspecialobject
1168
+ iseq.putspecialobject(opnds[0])
1169
+ when :send
1170
+ block_iseq = opnds[1] ? from(opnds[1], options, iseq) : nil
1171
+ block_iseqs << block_iseq if block_iseq
1172
+ iseq.send(CallData.from(opnds[0]), block_iseq)
1173
+ when :setclassvariable
1174
+ iseq.push(SetClassVariable.new(opnds[0], opnds[1]))
1175
+ when :setconstant
1176
+ iseq.setconstant(opnds[0])
1177
+ when :setglobal
1178
+ iseq.setglobal(opnds[0])
1179
+ when :setinstancevariable
1180
+ iseq.push(SetInstanceVariable.new(opnds[0], opnds[1]))
1181
+ when :setn
1182
+ iseq.setn(opnds[0])
1183
+ when :setspecial
1184
+ iseq.setspecial(opnds[0])
1185
+ when :splatarray
1186
+ iseq.splatarray(opnds[0])
1187
+ when :swap
1188
+ iseq.swap
1189
+ when :throw
1190
+ iseq.throw(opnds[0])
1191
+ when :topn
1192
+ iseq.topn(opnds[0])
1193
+ when :toregexp
1194
+ iseq.toregexp(opnds[0], opnds[1])
1195
+ else
1196
+ raise "Unknown instruction type: #{type}"
1197
+ end
1198
+ end
1199
+
1200
+ # set up the catch table
1201
+ source[12].each do |entry|
1202
+ case entry[0]
1203
+ when :break
1204
+ if entry[1]
1205
+ break_iseq =
1206
+ block_iseqs.find do |block_iseq|
1207
+ block_iseq.name == entry[1][5] &&
1208
+ block_iseq.file == entry[1][6] &&
1209
+ block_iseq.line == entry[1][8]
1210
+ end
1211
+
1212
+ iseq.catch_break(
1213
+ break_iseq || from(entry[1], options, iseq),
1214
+ labels[entry[2]],
1215
+ labels[entry[3]],
1216
+ labels[entry[4]],
1217
+ entry[5]
1218
+ )
1219
+ else
1220
+ iseq.catch_break(
1221
+ nil,
1222
+ labels[entry[2]],
1223
+ labels[entry[3]],
1224
+ labels[entry[4]],
1225
+ entry[5]
1226
+ )
1227
+ end
1228
+ when :ensure
1229
+ iseq.catch_ensure(
1230
+ from(entry[1], options, iseq),
1231
+ labels[entry[2]],
1232
+ labels[entry[3]],
1233
+ labels[entry[4]],
1234
+ entry[5]
1235
+ )
1236
+ when :next
1237
+ iseq.catch_next(
1238
+ labels[entry[2]],
1239
+ labels[entry[3]],
1240
+ labels[entry[4]],
1241
+ entry[5]
1242
+ )
1243
+ when :rescue
1244
+ iseq.catch_rescue(
1245
+ from(entry[1], options, iseq),
1246
+ labels[entry[2]],
1247
+ labels[entry[3]],
1248
+ labels[entry[4]],
1249
+ entry[5]
1250
+ )
1251
+ when :redo
1252
+ iseq.catch_redo(
1253
+ labels[entry[2]],
1254
+ labels[entry[3]],
1255
+ labels[entry[4]],
1256
+ entry[5]
1257
+ )
1258
+ when :retry
1259
+ iseq.catch_retry(
1260
+ labels[entry[2]],
1261
+ labels[entry[3]],
1262
+ labels[entry[4]],
1263
+ entry[5]
1264
+ )
1265
+ else
1266
+ raise "unknown catch type: #{entry[0]}"
1267
+ end
1268
+ end
1269
+
1270
+ iseq.compile! if iseq.type == :top
1271
+ iseq
1272
+ end
1273
+ end
1274
+ end
1275
+ end