forked from rubinius/rubinius
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinstruction_parser.rb
973 lines (798 loc) · 23.1 KB
/
instruction_parser.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
# A simple parser to transform a description of the VM instructions into
# various implementation files.
#
# The parser recognizes three forms presently:
#
# 1. define NAME <value>
# 2. section "Some description"
# 3. instruction opcode_name(arg1, arg2) [ consumed -- produced ]
# body
# end
#
# Once the forms are parsed, each type (a subclass of Definition) implements
# methods to emit its form in the context of a particular generator. In other
# words, the generated files are produced by a set of methods, each of which
# simply iterate the array of definitions in order calling a method for that
# generator.
#
# For example, to generate the opcode definitions file, the #opcode_definition
# method is called on each definition parsed from the 'instructions.def' file.
#
# The format of the instruction definition needs addition explanation. The
# instructions can take 0, 1, or 2 arguments. The arguments are listed in
# parenthesis after the instruction name.
#
# Each instruction has a stack effect, or the stack items consumed and the
# stack items produced. These are described in the square brackets after the
# argument list a la Forth. The '--' symbol separates the consumed from the
# produced.
#
# The names of the stack items are purely for documentation except in the
# special case of an item named like '+count'. This special form refers to one
# of the instruction's arguments and means that the total number of stack
# items consumed is the sum of the explicit stack items listed and the value
# of the instruction's argument.
#
# For example, the 'make_array' instruction consumes 'count' items on the
# stack, but no additional items. While the 'send_stack' instruction consumes
# the 'receiver' and 'count' additional items.
#
# Finally, for instructions that alter the normal sequential flow of execution,
# the type of control flow change will be specified in curly braces, e.g.
# {branch}. The control flow behavior defaults to next, but for those
# instructions where control is (or may) change, one of the following flow
# types must be specified:
# branch - for instructions that branch within the same method
# send - for instructions that invoke another method
# yield - for instructions that yield to a block
# return - for instructions that return to the calling method
# raise - for instructions that invoke exception handling
class InstructionParser
# Processes an opcode definition from the instruction parser into a method
# called by the bytecode compiler's Generator to emit the instruction
# stream.
class GeneratorMethod
def initialize(parser, file, opcode)
@parser = parser
@file = file
@opcode = opcode
@method_name = nil
@before_stream = nil
@after_stream = nil
@before_stack = nil
@after_stack = nil
end
def method_name
@method_name || @opcode.name
end
def process
__send__ :"process_#{@opcode.name}"
end
# Define the method signature.
def method_signature
case @opcode.arguments.size
when 0
@file.puts " def #{method_name}"
when 1
@file.puts " def #{method_name}(arg1)"
when 2
@file.puts " def #{method_name}(arg1, arg2)"
end
end
# Append to the opcode stream.
def method_append_stream
case @opcode.arguments.size
when 0
@file.puts " @stream << #{@opcode.bytecode}"
when 1
@file.puts " @stream << #{@opcode.bytecode} << arg1"
when 2
@file.puts " @stream << #{@opcode.bytecode} << arg1 << arg2"
end
@file.puts " @ip += #{@opcode.arguments.size + 1}"
end
# Calculate the full stack affect of this opcode.
def method_stack_effect
read = @opcode.static_read_effect
write = @opcode.static_write_effect
if @opcode.extra
if read > 0
read = "arg#{@opcode.extra+1}+#{read}"
else
read = "arg#{@opcode.extra+1}"
end
end
if @opcode.produced_extra
if write > 0
write = "(arg#{@opcode.produced_extra+1} * #{@opcode.produced_times})+#{write}"
else
write = "(arg#{@opcode.produced_extra+1} * #{@opcode.produced_times})"
end
end
@file.puts " @current_block.add_stack(#{read}, #{write})"
end
def method_close
@file.puts " @instruction = #{@opcode.bytecode}"
@file.puts " end"
@file.puts ""
end
def method_definition
method_signature
@before_stream.call if @before_stream
method_append_stream
@after_stream.call if @after_stream
@before_stack.call if @before_stack
method_stack_effect
@after_stack.call if @after_stack
method_close
end
def literal_method
@before_stream = lambda { @file.puts " arg1 = find_literal arg1" }
method_definition
end
def literal_count_method
@before_stream = lambda do
@file.puts " arg1 = find_literal arg1"
@file.puts " arg2 = Integer(arg2)"
end
method_definition
end
def method_missing(sym, *args)
method_definition
end
# Basic block creation, closing, transitions
def bb_unconditional_branch
@file.puts " @current_block.left = arg1.basic_block"
@file.puts " @current_block.close"
@file.puts " @current_block = new_basic_block"
end
def bb_conditional_branch
@file.puts " @current_block.left = arg1.basic_block"
@file.puts " @current_block.close"
@file.puts " block = new_basic_block"
@file.puts " @current_block.right = block"
@file.puts " @current_block = block"
end
def bb_exit(check_stack)
@file.puts " @current_block.close #{check_stack}"
@file.puts " @current_block = new_basic_block"
end
# Categories of instructions. These could be done based on the control
# flow attributes added to the instructions for the debugger, but that
# would require more complicated parsing and contortions to work around
# potential special cases. Instead, the simple #process_<opcode> method
# is used and redundancy is factored out as needed.
def unconditional_branch
@before_stream = lambda { @file.puts " location = @ip + 1" }
@after_stream = lambda { @file.puts " arg1.used_at location" }
@after_stack = lambda { bb_unconditional_branch }
method_definition
end
def conditional_branch
@before_stream = lambda { @file.puts " location = @ip + 1" }
@after_stream = lambda { @file.puts " arg1.used_at location" }
@after_stack = lambda { bb_conditional_branch }
method_definition
end
def unconditional_exit(check_stack=false)
@after_stack = lambda { bb_exit check_stack }
method_definition
end
# Specific instruction methods
def process_goto
unconditional_branch
end
def process_goto_if_false
conditional_branch
end
def process_goto_if_true
conditional_branch
end
def process_setup_unwind
conditional_branch
end
def process_ret
unconditional_exit true
end
def process_raise_exc
unconditional_exit
end
def process_raise_return
unconditional_exit true
end
def process_ensure_return
unconditional_exit true
end
def process_raise_break
unconditional_exit
end
def process_reraise
unconditional_exit
end
def process_push_int
method_signature
# Integers greater than 256 are stored in the literals tuple.
@file.puts <<EOM
if arg1 > 2 and arg1 < 256
@stream << #{@opcode.bytecode} << arg1
@current_block.add_stack(0, 1)
@ip += 2
@instruction = #{@opcode.bytecode}
else
case arg1
when -1
meta_push_neg_1
when 0
meta_push_0
when 1
meta_push_1
when 2
meta_push_2
else
push_literal arg1
end
end
end
EOM
end
def process_push_literal
@method_name = :emit_push_literal
method_definition
end
def process_dup_top
method_definition
end
def process_swap_stack
method_definition
end
def process_push_const
# unused right now
end
def process_find_const
literal_method
end
def process_push_ivar
literal_method
end
def process_set_ivar
literal_method
end
def process_check_serial
literal_count_method
end
def process_check_serial_private
literal_count_method
end
def process_create_block
@before_stream = lambda do
@file.puts " arg1 = add_literal arg1"
@file.puts " @generators << arg1"
end
method_definition
end
def process_invoke_primitive
literal_count_method
end
def process_call_custom
literal_count_method
end
def process_zsuper
literal_method
end
def process_cast_array
method_signature
make_array = @parser.find_opcode "make_array"
@file.puts " unless @instruction == #{@opcode.bytecode} or @instruction == #{make_array.bytecode}"
@file.puts " @stream << #{@opcode.bytecode}"
@file.puts " @ip += 1"
@file.puts " end"
method_close
end
end
class ParseError < Exception; end
class Definition
def opcode_definition(file)
end
def opcode_method(parser, file)
end
def opcode_name
end
def opcode_width
end
def opcode_prototype
end
def opcode_location
end
def opcode_implementation(file)
end
def opcode_enum
end
def opcode_define
end
def opcode_visitor
end
def opcode_stack_effect(file)
end
def opcode_documentation(file)
end
end
class Section < Definition
attr_reader :heading
def initialize(line, doc)
@heading = line.strip[1..-2]
end
def opcode_definition(file)
file.print "\n # "
file.puts @heading
end
end
class Define < Definition
attr_reader :name, :value
def initialize(line, doc)
@name, @value = line.strip.split
end
def opcode_definition(file)
file.puts
file.puts " #{@name} = #{@value}"
file.puts
end
def opcode_define
"#define #{@name} #{@value}"
end
end
class InstructionDocumentation
def initialize(instruction)
@instruction = instruction
@description = []
@consumed = nil
@produced = nil
@see_also = []
@notes = []
@example = []
end
def parse(text)
section = []
text.each do |line|
case line
when /\[Description\]/
section = @description
when /\[Stack Before\]/
section = @consumed = []
when /\[Stack After\]/
section = @produced = []
when /\[See Also\]/
section = @see_also
when /\[Notes\]/
section = @notes
when /\[Example\]/
section = @example
else
section << line[1..-1]
end
end
self
end
def consumed
# TODO: account for extra consumed
@consumed || @instruction.consumed + ["..."]
end
def produced
# TODO: account for extra produced
@produced || @instruction.produced + ["..."]
end
def format(file)
file.puts %[<h3><a class="instruction" name="#{name}">#{name}(#{arguments})</a></h3>]
end
def stack_effect(file)
c = consumed
p = produced
n = c.size > p.size ? c.size : p.size
file.puts %[\n<table class="stack_effect">]
file.puts "<thead>"
file.puts "<tr><th>Before</th><th>After</th></tr>"
file.puts "</thead>"
file.puts "<tbody>"
n.times do |i|
file.puts "<tr><td>#{c[i]}</td><td>#{p[i]}</td></tr>"
end
file.puts "</tbody>"
file.puts "</table>"
end
def description(file)
file.puts ""
file.puts @description
file.puts ""
end
def example(file)
return if @example.empty?
file.puts ""
file.puts '#### Example'
file.puts @example
file.puts ""
end
def see_also(file)
return if @see_also.empty?
file.puts "\n<h4>See Also</h4>"
file.puts %[<ul class="insn_cross_ref">]
@see_also.each do |x|
x = x.strip
file.puts %[<li><a href="\##{x}">#{x}</a></li>]
end
file.puts "</ul>"
end
def notes(file)
return if @notes.empty?
file.puts ""
file.puts '#### Notes'
file.puts @notes
file.puts ""
end
def name
@instruction.name
end
def arguments
@instruction.arguments.join ", "
end
def render(file)
format file
description file
stack_effect file
example file
notes file
see_also file
end
end
class Instruction < Definition
attr_reader :name, :bytecode, :arguments, :consumed, :extra,
:produced, :produced_extra, :effect, :body, :control_flow,
:produced_times
def self.bytecodes
@bytecodes
end
def self.bytecode
@bytecodes ||= -1
@bytecodes += 1
end
def initialize(parser, header, doc)
@parser = parser
@file = parser.file
@header = header
@body = []
@doc = InstructionDocumentation.new(self).parse(doc)
@extra = nil
@produced_extra = nil
@bytecode = self.class.bytecode
@control_flow = :next
end
def parse
parse_header
parse_body
end
def parse_header
m = @header.match(/(\w+)\(([^)]*)\) \[ ([\w+ ]*)-- ([\w+ ]*)\]( =>\s*(\w+))?/)
unless m
raise ParseError, "invalid instruction header '#{@header}' at #{@file.lineno}"
end
@name = m[1]
@arguments = m[2].strip.split
consumed = m[3].strip.split
last = consumed.last
if last and last[0] == ?+
argument = last[1..-1]
unless @extra = @arguments.index(argument)
raise ParseError, "no argument named '#{arg}' at #{@file.lineno}"
end
consumed.pop
end
@consumed = consumed
@produced = m[4].strip.split
last = @produced.last
if last and last[0] == ?+
@produced_times = 0
while last[0] == ?+
@produced_times += 1
last = last[1..-1]
end
unless @produced_extra = @arguments.index(argument)
raise ParseError, "no argument named '#{arg}' at #{@file.lineno}"
end
@produced.pop
end
@effect = @produced.size - @consumed.size
@control_flow = m[6].to_sym if m[6]
end
def static_read_effect
@consumed.size
end
def static_write_effect
@produced.size
end
def parse_body
while line = @file.gets
return if line =~ /^end\b/
@body << line
end
end
def opcode_definition(file)
if @extra
consumed = "[#{@consumed.size},#{@extra+1}]"
else
consumed = "#{@consumed.size}"
end
if @produced_extra
produced = "[#{@produced.size}, #{@produced_extra+1}, #{@produced_times}]"
else
produced = "#{@produced.size}"
end
file.print " opcode %2d, :%-28s " % [@bytecode, @name + ","]
stack = "[#{consumed}, #{produced}],"
file.print ":stack => %-12s" % stack
syms = @arguments.map { |x| ":#{x}" }.join(", ")
args = "[#{syms}],"
file.print ":args => %-20s" % args
file.puts " :control_flow => :#{@control_flow}"
end
def opcode_method(parser, file)
GeneratorMethod.new(parser, file, self).process
end
def opcode_name
"op_#{@name}"
end
def opcode_width
@arguments.size + 1
end
def opcode_prototype
name = opcode_name
indent = " " * (name.size + 9)
prototype = "Object* #{name}(rubinius::VM* state, rubinius::VMMethod* vmm,
#{indent}rubinius::InterpreterCallFrame* const call_frame,
#{indent}rubinius::VMMethod::InterpreterState& is"
unless @arguments.empty?
prototype << ",\n#{indent}"
prototype << @arguments.map { |a| "int #{a}" }.join(", ")
end
prototype << ");\n"
end
def opcode_enum
"insn_#{@name}"
end
def opcode_location
"op_impl_#{@name}"
end
def opcode_implementation(file)
file.puts " #{opcode_location}: {"
@arguments.each do |arg|
file.puts " intptr_t #{arg} = next_int;"
end
@body.each do |line|
file.puts " #{line}".rstrip
end
end
def opcode_visitor
"HANDLE_INST#{@arguments.size}(#{@bytecode}, #{@name});"
end
def opcode_stack_effect(file)
file.puts "case InstructionSequence::insn_#{@name}:"
read = static_read_effect
write = static_write_effect
if @extra
if read > 0
read = "operand#{@extra+1}+#{read}"
else
read = "operand#{@extra+1}"
end
end
if @produced_extra
if write > 0
write = "(operand#{@produced_extra+1} * #{@produced_times})+#{write}"
else
write = "(operand#{@produced_extra+1} * #{@produced_times})"
end
end
file.puts "if(read_effect) { *read_effect = (#{read}); }"
file.puts "if(write_effect) { *write_effect = (#{write}); }"
file.puts "return (#{write}) - (#{read});"
end
def opcode_documentation(file)
@doc.render file
end
end
# InstructionParser methods
attr_reader :file, :objects
def initialize(filename)
@filename = filename
@objects = []
@parsed = false
@blank_line = false
@doc = []
end
def process_define(parser, line, doc)
parser.objects << Define.new(line, doc)
end
def process_section(parser, line, doc)
parser.objects << Section.new(line, doc)
end
def process_instruction(parser, line, doc)
insn = Instruction.new parser, line, doc
insn.parse
parser.objects << insn
end
def parse
return if @parsed
File.open @filename, "rb" do |file|
@file = file
while line = @file.gets
if index = line.index("#")
if index == 0
if @blank_line
@doc.clear
@blank_line = false
end
@doc << line
end
line = line[0, index]
elsif line.strip.empty?
@blank_line = true
next
end
if m = line.match(/^(instruction|section|define) +(.*)$/)
send :"process_#{m[1]}", self, m[2], @doc
@doc.clear
end
end
end
@parsed = true
end
def find_opcode(name)
@objects.find { |x| x.kind_of? Instruction and x.name == name }
end
# Generated output methods
def generate_opcodes(filename)
File.open filename, "wb" do |file|
file.puts "# *** This file is generated by InstructionParser ***"
file.puts
file.puts "module Rubinius"
file.puts " class InstructionSet"
objects.each { |obj| obj.opcode_definition file }
file.puts " end"
file.puts "end"
end
end
def generate_generator_methods(filename)
File.open filename, "wb" do |file|
file.puts "# *** This file is generated by InstructionParser ***"
file.puts
file.puts "module Rubinius"
file.puts " module GeneratorMethods"
objects.each { |obj| obj.opcode_method self, file }
file.puts " end"
file.puts "end"
end
end
def generate_names(filename)
File.open filename, "wb" do |file|
file.puts "const char *rubinius::InstructionSequence::get_instruction_name(int op) {"
file.puts " static const char instruction_names[] = {"
offset = 0
offsets = []
objects.each do |obj|
if name = obj.opcode_name
file.puts " \"#{name}\\0\""
offsets << offset
offset += name.size + 1
end
end
file.puts " };"
file.puts
file.puts " static const unsigned int instruction_name_offsets[] = {"
file.print " "
file.puts offsets.join(",\n ")
file.puts " };"
file.puts
file.puts " return instruction_names + instruction_name_offsets[op];"
file.puts "}"
end
end
def generate_names_header(filename)
File.open filename, "wb" do |file|
file.puts "static const char *get_instruction_name(int op);"
file.puts "typedef enum {"
objects.each do |obj|
if enum = obj.opcode_enum
file.puts " #{enum} = #{obj.bytecode},"
end
end
file.puts "} instruction_names;"
file.puts "const static unsigned int cTotal = #{Instruction.bytecodes};"
end
end
def generate_prototypes(filename)
File.open filename, "wb" do |file|
file.puts "extern \"C\" {"
objects.each do |obj|
if prototype = obj.opcode_prototype
file.puts prototype
end
end
file.puts "} // extern \"C\""
end
end
def generate_sizes(filename)
File.open filename, "wb" do |file|
file.puts "size_t width = 0;"
file.puts "switch(op) {"
objects.each do |obj|
if width = obj.opcode_width
file.puts " case #{obj.bytecode}:"
file.puts " width = #{width}; break;"
end
end
file.puts "}"
end
end
def generate_locations(filename)
File.open filename, "wb" do |file|
file.puts " static const void* insn_locations[] = {"
objects.each do |obj|
if location = obj.opcode_location
file.puts " &&#{location},"
end
end
file.puts " NULL"
file.puts " };"
end
end
def generate_implementations(filename)
File.open filename, "wb" do |file|
file.puts " DISPATCH;"
objects.each do |obj|
if obj.opcode_implementation(file)
file.puts " DISPATCH;"
file.puts " }"
end
end
end
end
def generate_defines(filename)
File.open filename, "wb" do |file|
objects.each do |obj|
if define = obj.opcode_define
file.puts define
end
end
end
end
def generate_visitors(filename)
File.open filename, "wb" do |file|
objects.each do |obj|
if visitor = obj.opcode_visitor
file.puts visitor
end
end
end
end
def generate_stack_effects(filename)
File.open filename, "wb" do |file|
file.puts "static inline int stack_difference(opcode op,"
file.puts " opcode operand1 = 0,"
file.puts " opcode operand2 = 0,"
file.puts " int* read_effect = 0, int* write_effect = 0)"
file.puts "{"
file.puts " switch(op) {"
objects.each do |obj|
obj.opcode_stack_effect file
end
file.puts " }"
file.puts " abort();"
file.puts " return 0;"
file.puts "}"
end
end
def generate_documentation(filename)
File.open filename, "wb" do |file|
objects.each do |obj|
obj.opcode_documentation file
end
end
end
end