OSDN Git Service

Merge "[MIPS] Dalvik fast interpreter support and JIT implementation"
[android-x86/dalvik.git] / vm / mterp / gen-mterp.py
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2007 The Android Open Source Project
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #      http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 #
18 # Using instructions from an architecture-specific config file, generate C
19 # and assembly source files for the Dalvik interpreter.
20 #
21
22 import sys, string, re, time
23 from string import Template
24
25 interp_defs_file = "../../libdex/DexOpcodes.h" # need opcode list
26 kNumPackedOpcodes = 512 # TODO: Derive this from DexOpcodes.h.
27
28 splitops = False
29 verbose = False
30 handler_size_bits = -1000
31 handler_size_bytes = -1000
32 in_op_start = 0             # 0=not started, 1=started, 2=ended
33 in_alt_op_start = 0         # 0=not started, 1=started, 2=ended
34 default_op_dir = None
35 default_alt_stub = None
36 opcode_locations = {}
37 alt_opcode_locations = {}
38 asm_stub_text = []
39 label_prefix = ".L"         # use ".L" to hide labels from gdb
40 alt_label_prefix = ".L_ALT" # use ".L" to hide labels from gdb
41 style = None                # interpreter style
42 generate_alt_table = False
43
44 # Exception class.
45 class DataParseError(SyntaxError):
46     "Failure when parsing data file"
47
48 #
49 # Set any omnipresent substitution values.
50 #
51 def getGlobalSubDict():
52     return { "handler_size_bits":handler_size_bits,
53              "handler_size_bytes":handler_size_bytes }
54
55 #
56 # Parse arch config file --
57 # Set interpreter style.
58 #
59 def setHandlerStyle(tokens):
60     global style
61     if len(tokens) != 2:
62         raise DataParseError("handler-style requires one argument")
63     style = tokens[1]
64     if style != "computed-goto" and style != "jump-table" and style != "all-c":
65         raise DataParseError("handler-style (%s) invalid" % style)
66
67 #
68 # Parse arch config file --
69 # Set handler_size_bytes to the value of tokens[1], and handler_size_bits to
70 # log2(handler_size_bytes).  Throws an exception if "bytes" is not 0 or
71 # a power of two.
72 #
73 def setHandlerSize(tokens):
74     global handler_size_bits, handler_size_bytes
75     if style != "computed-goto":
76         print "Warning: handler-size valid only for computed-goto interpreters"
77     if len(tokens) != 2:
78         raise DataParseError("handler-size requires one argument")
79     if handler_size_bits != -1000:
80         raise DataParseError("handler-size may only be set once")
81
82     # compute log2(n), and make sure n is 0 or a power of 2
83     handler_size_bytes = bytes = int(tokens[1])
84     bits = -1
85     while bytes > 0:
86         bytes //= 2     # halve with truncating division
87         bits += 1
88
89     if handler_size_bytes == 0 or handler_size_bytes != (1 << bits):
90         raise DataParseError("handler-size (%d) must be power of 2" \
91                 % orig_bytes)
92     handler_size_bits = bits
93
94 #
95 # Parse arch config file --
96 # Copy a file in to the C or asm output file.
97 #
98 def importFile(tokens):
99     if len(tokens) != 2:
100         raise DataParseError("import requires one argument")
101     source = tokens[1]
102     if source.endswith(".cpp"):
103         appendSourceFile(tokens[1], getGlobalSubDict(), c_fp, None)
104     elif source.endswith(".S"):
105         appendSourceFile(tokens[1], getGlobalSubDict(), asm_fp, None)
106     else:
107         raise DataParseError("don't know how to import %s (expecting .cpp/.S)"
108                 % source)
109
110 #
111 # Parse arch config file --
112 # Copy a file in to the C or asm output file.
113 #
114 def setAsmStub(tokens):
115     global asm_stub_text
116     if style == "all-c":
117         print "Warning: asm-stub ignored for all-c interpreter"
118     if len(tokens) != 2:
119         raise DataParseError("import requires one argument")
120     try:
121         stub_fp = open(tokens[1])
122         asm_stub_text = stub_fp.readlines()
123     except IOError, err:
124         stub_fp.close()
125         raise DataParseError("unable to load asm-stub: %s" % str(err))
126     stub_fp.close()
127
128 #
129 # Parse arch config file --
130 # Record location of default alt stub
131 #
132 def setAsmAltStub(tokens):
133     global default_alt_stub, generate_alt_table
134     if style == "all-c":
135         print "Warning: asm-alt-stub ingored for all-c interpreter"
136     if len(tokens) != 2:
137         raise DataParseError("import requires one argument")
138     default_alt_stub = tokens[1]
139     generate_alt_table = True
140
141 #
142 # Parse arch config file --
143 # Start of opcode list.
144 #
145 def opStart(tokens):
146     global in_op_start
147     global default_op_dir
148     if len(tokens) != 2:
149         raise DataParseError("opStart takes a directory name argument")
150     if in_op_start != 0:
151         raise DataParseError("opStart can only be specified once")
152     default_op_dir = tokens[1]
153     in_op_start = 1
154
155 #
156 # Parse arch config file --
157 # Set location of a single alt opcode's source file.
158 #
159 def altEntry(tokens):
160     global generate_alt_table
161     if len(tokens) != 3:
162         raise DataParseError("alt requires exactly two arguments")
163     if in_op_start != 1:
164         raise DataParseError("alt statements must be between opStart/opEnd")
165     try:
166         index = opcodes.index(tokens[1])
167     except ValueError:
168         raise DataParseError("unknown opcode %s" % tokens[1])
169     if alt_opcode_locations.has_key(tokens[1]):
170         print "Note: alt overrides earlier %s (%s -> %s)" \
171                 % (tokens[1], alt_opcode_locations[tokens[1]], tokens[2])
172     alt_opcode_locations[tokens[1]] = tokens[2]
173     generate_alt_table = True
174
175 #
176 # Parse arch config file --
177 # Set location of a single opcode's source file.
178 #
179 def opEntry(tokens):
180     #global opcode_locations
181     if len(tokens) != 3:
182         raise DataParseError("op requires exactly two arguments")
183     if in_op_start != 1:
184         raise DataParseError("op statements must be between opStart/opEnd")
185     try:
186         index = opcodes.index(tokens[1])
187     except ValueError:
188         raise DataParseError("unknown opcode %s" % tokens[1])
189     if opcode_locations.has_key(tokens[1]):
190         print "Note: op overrides earlier %s (%s -> %s)" \
191                 % (tokens[1], opcode_locations[tokens[1]], tokens[2])
192     opcode_locations[tokens[1]] = tokens[2]
193
194 #
195 # Emit jump table
196 #
197 def emitJmpTable(start_label, prefix):
198     asm_fp.write("\n    .global %s\n" % start_label)
199     asm_fp.write("    .text\n")
200     asm_fp.write("%s:\n" % start_label)
201     for i in xrange(kNumPackedOpcodes):
202         op = opcodes[i]
203         dict = getGlobalSubDict()
204         dict.update({ "opcode":op, "opnum":i })
205         asm_fp.write("    .long " + prefix + \
206                      "_%(opcode)s /* 0x%(opnum)02x */\n" % dict)
207
208 #
209 # Parse arch config file --
210 # End of opcode list; emit instruction blocks.
211 #
212 def opEnd(tokens):
213     global in_op_start
214     if len(tokens) != 1:
215         raise DataParseError("opEnd takes no arguments")
216     if in_op_start != 1:
217         raise DataParseError("opEnd must follow opStart, and only appear once")
218     in_op_start = 2
219
220     loadAndEmitOpcodes()
221     if splitops == False:
222         if generate_alt_table:
223             loadAndEmitAltOpcodes()
224             if style == "jump-table":
225                 emitJmpTable("dvmAsmInstructionStart", label_prefix);
226                 emitJmpTable("dvmAsmAltInstructionStart", alt_label_prefix);
227
228 def genaltop(tokens):
229     if in_op_start != 2:
230        raise DataParseError("alt-op can be specified only after op-end")
231     if len(tokens) != 1:
232         raise DataParseError("opEnd takes no arguments")
233     if generate_alt_table:
234         loadAndEmitAltOpcodes()
235         if style == "jump-table":
236             emitJmpTable("dvmAsmInstructionStart", label_prefix);
237             emitJmpTable("dvmAsmAltInstructionStart", alt_label_prefix);
238
239
240 #
241 # Extract an ordered list of instructions from the VM sources.  We use the
242 # "goto table" definition macro, which has exactly kNumPackedOpcodes
243 # entries.
244 #
245 def getOpcodeList():
246     opcodes = []
247     opcode_fp = open(interp_defs_file)
248     opcode_re = re.compile(r"^\s*H\(OP_(\w+)\),.*", re.DOTALL)
249     for line in opcode_fp:
250         match = opcode_re.match(line)
251         if not match:
252             continue
253         opcodes.append("OP_" + match.group(1))
254     opcode_fp.close()
255
256     if len(opcodes) != kNumPackedOpcodes:
257         print "ERROR: found %d opcodes in Interp.h (expected %d)" \
258                 % (len(opcodes), kNumPackedOpcodes)
259         raise SyntaxError, "bad opcode count"
260     return opcodes
261
262 def emitAlign():
263     if style == "computed-goto":
264         asm_fp.write("    .balign %d\n" % handler_size_bytes)
265
266 #
267 # Load and emit opcodes for all kNumPackedOpcodes instructions.
268 #
269 def loadAndEmitOpcodes():
270     sister_list = []
271     assert len(opcodes) == kNumPackedOpcodes
272     need_dummy_start = False
273     if style == "jump-table":
274         start_label = "dvmAsmInstructionStartCode"
275         end_label = "dvmAsmInstructionEndCode"
276     else:
277         start_label = "dvmAsmInstructionStart"
278         end_label = "dvmAsmInstructionEnd"
279
280     # point dvmAsmInstructionStart at the first handler or stub
281     asm_fp.write("\n    .global %s\n" % start_label)
282     asm_fp.write("    .type   %s, %%function\n" % start_label)
283     asm_fp.write("%s = " % start_label + label_prefix + "_OP_NOP\n")
284     asm_fp.write("    .text\n\n")
285
286     for i in xrange(kNumPackedOpcodes):
287         op = opcodes[i]
288
289         if opcode_locations.has_key(op):
290             location = opcode_locations[op]
291         else:
292             location = default_op_dir
293
294         if location == "c":
295             loadAndEmitC(location, i)
296             if len(asm_stub_text) == 0:
297                 need_dummy_start = True
298         else:
299             loadAndEmitAsm(location, i, sister_list)
300
301     # For a 100% C implementation, there are no asm handlers or stubs.  We
302     # need to have the dvmAsmInstructionStart label point at OP_NOP, and it's
303     # too annoying to try to slide it in after the alignment psuedo-op, so
304     # we take the low road and just emit a dummy OP_NOP here.
305     if need_dummy_start:
306         emitAlign()
307         asm_fp.write(label_prefix + "_OP_NOP:   /* dummy */\n");
308
309     emitAlign()
310     asm_fp.write("    .size   %s, .-%s\n" % (start_label, start_label))
311     asm_fp.write("    .global %s\n" % end_label)
312     asm_fp.write("%s:\n" % end_label)
313
314     if style == "computed-goto":
315         emitSectionComment("Sister implementations", asm_fp)
316         asm_fp.write("    .global dvmAsmSisterStart\n")
317         asm_fp.write("    .type   dvmAsmSisterStart, %function\n")
318         asm_fp.write("    .text\n")
319         asm_fp.write("    .balign 4\n")
320         asm_fp.write("dvmAsmSisterStart:\n")
321         asm_fp.writelines(sister_list)
322         asm_fp.write("\n    .size   dvmAsmSisterStart, .-dvmAsmSisterStart\n")
323         asm_fp.write("    .global dvmAsmSisterEnd\n")
324         asm_fp.write("dvmAsmSisterEnd:\n\n")
325
326 #
327 # Load an alternate entry stub
328 #
329 def loadAndEmitAltStub(source, opindex):
330     op = opcodes[opindex]
331     if verbose:
332         print " alt emit %s --> stub" % source
333     dict = getGlobalSubDict()
334     dict.update({ "opcode":op, "opnum":opindex })
335
336     emitAsmHeader(asm_fp, dict, alt_label_prefix)
337     appendSourceFile(source, dict, asm_fp, None)
338
339 #
340 # Load and emit alternate opcodes for all kNumPackedOpcodes instructions.
341 #
342 def loadAndEmitAltOpcodes():
343     assert len(opcodes) == kNumPackedOpcodes
344     if style == "jump-table":
345         start_label = "dvmAsmAltInstructionStartCode"
346         end_label = "dvmAsmAltInstructionEndCode"
347     else:
348         start_label = "dvmAsmAltInstructionStart"
349         end_label = "dvmAsmAltInstructionEnd"
350
351     # point dvmAsmInstructionStart at the first handler or stub
352     asm_fp.write("\n    .global %s\n" % start_label)
353     asm_fp.write("    .type   %s, %%function\n" % start_label)
354     asm_fp.write("    .text\n\n")
355     asm_fp.write("%s = " % start_label + label_prefix + "_ALT_OP_NOP\n")
356
357     for i in xrange(kNumPackedOpcodes):
358         op = opcodes[i]
359         if alt_opcode_locations.has_key(op):
360             source = "%s/ALT_%s.S" % (alt_opcode_locations[op], op)
361         else:
362             source = default_alt_stub
363         loadAndEmitAltStub(source, i)
364
365     emitAlign()
366     asm_fp.write("    .size   %s, .-%s\n" % (start_label, start_label))
367     asm_fp.write("    .global %s\n" % end_label)
368     asm_fp.write("%s:\n" % end_label)
369
370 #
371 # Load a C fragment and emit it, then output an assembly stub.
372 #
373 def loadAndEmitC(location, opindex):
374     op = opcodes[opindex]
375     source = "%s/%s.cpp" % (location, op)
376     if verbose:
377         print " emit %s --> C++" % source
378     dict = getGlobalSubDict()
379     dict.update({ "opcode":op, "opnum":opindex })
380
381     appendSourceFile(source, dict, c_fp, None)
382
383     if len(asm_stub_text) != 0:
384         emitAsmStub(asm_fp, dict)
385
386 #
387 # Load an assembly fragment and emit it.
388 #
389 def loadAndEmitAsm(location, opindex, sister_list):
390     op = opcodes[opindex]
391     source = "%s/%s.S" % (location, op)
392     dict = getGlobalSubDict()
393     dict.update({ "opcode":op, "opnum":opindex })
394     if verbose:
395         print " emit %s --> asm" % source
396
397     emitAsmHeader(asm_fp, dict, label_prefix)
398     appendSourceFile(source, dict, asm_fp, sister_list)
399
400 #
401 # Output the alignment directive and label for an assembly piece.
402 #
403 def emitAsmHeader(outfp, dict, prefix):
404     outfp.write("/* ------------------------------ */\n")
405     # The alignment directive ensures that the handler occupies
406     # at least the correct amount of space.  We don't try to deal
407     # with overflow here.
408     emitAlign()
409     # Emit a label so that gdb will say the right thing.  We prepend an
410     # underscore so the symbol name doesn't clash with the Opcode enum.
411     outfp.write(prefix + "_%(opcode)s: /* 0x%(opnum)02x */\n" % dict)
412
413 #
414 # Output a generic instruction stub that updates the "glue" struct and
415 # calls the C implementation.
416 #
417 def emitAsmStub(outfp, dict):
418     emitAsmHeader(outfp, dict, label_prefix)
419     for line in asm_stub_text:
420         templ = Template(line)
421         outfp.write(templ.substitute(dict))
422
423 #
424 # Append the file specified by "source" to the open "outfp".  Each line will
425 # be template-replaced using the substitution dictionary "dict".
426 #
427 # If the first line of the file starts with "%" it is taken as a directive.
428 # A "%include" line contains a filename and, optionally, a Python-style
429 # dictionary declaration with substitution strings.  (This is implemented
430 # with recursion.)
431 #
432 # If "sister_list" is provided, and we find a line that contains only "&",
433 # all subsequent lines from the file will be appended to sister_list instead
434 # of copied to the output.
435 #
436 # This may modify "dict".
437 #
438 def appendSourceFile(source, dict, outfp, sister_list):
439     outfp.write("/* File: %s */\n" % source)
440     infp = open(source, "r")
441     in_sister = False
442     for line in infp:
443         if line.startswith("%include"):
444             # Parse the "include" line
445             tokens = line.strip().split(' ', 2)
446             if len(tokens) < 2:
447                 raise DataParseError("malformed %%include in %s" % source)
448
449             alt_source = tokens[1].strip("\"")
450             if alt_source == source:
451                 raise DataParseError("self-referential %%include in %s"
452                         % source)
453
454             new_dict = dict.copy()
455             if len(tokens) == 3:
456                 new_dict.update(eval(tokens[2]))
457             #print " including src=%s dict=%s" % (alt_source, new_dict)
458             appendSourceFile(alt_source, new_dict, outfp, sister_list)
459             continue
460
461         elif line.startswith("%default"):
462             # copy keywords into dictionary
463             tokens = line.strip().split(' ', 1)
464             if len(tokens) < 2:
465                 raise DataParseError("malformed %%default in %s" % source)
466             defaultValues = eval(tokens[1])
467             for entry in defaultValues:
468                 dict.setdefault(entry, defaultValues[entry])
469             continue
470
471         elif line.startswith("%verify"):
472             # more to come, someday
473             continue
474
475         elif line.startswith("%break") and sister_list != None:
476             # allow more than one %break, ignoring all following the first
477             if style == "computed-goto" and not in_sister:
478                 in_sister = True
479                 sister_list.append("\n/* continuation for %(opcode)s */\n"%dict)
480             continue
481
482         # perform keyword substitution if a dictionary was provided
483         if dict != None:
484             templ = Template(line)
485             try:
486                 subline = templ.substitute(dict)
487             except KeyError, err:
488                 raise DataParseError("keyword substitution failed in %s: %s"
489                         % (source, str(err)))
490             except:
491                 print "ERROR: substitution failed: " + line
492                 raise
493         else:
494             subline = line
495
496         # write output to appropriate file
497         if in_sister:
498             sister_list.append(subline)
499         else:
500             outfp.write(subline)
501     outfp.write("\n")
502     infp.close()
503
504 #
505 # Emit a C-style section header comment.
506 #
507 def emitSectionComment(str, fp):
508     equals = "========================================" \
509              "==================================="
510
511     fp.write("\n/*\n * %s\n *  %s\n * %s\n */\n" %
512         (equals, str, equals))
513
514
515 #
516 # ===========================================================================
517 # "main" code
518 #
519
520 #
521 # Check args.
522 #
523 if len(sys.argv) != 3:
524     print "Usage: %s target-arch output-dir" % sys.argv[0]
525     sys.exit(2)
526
527 target_arch = sys.argv[1]
528 output_dir = sys.argv[2]
529
530 #
531 # Extract opcode list.
532 #
533 opcodes = getOpcodeList()
534 #for op in opcodes:
535 #    print "  %s" % op
536
537 #
538 # Open config file.
539 #
540 try:
541     config_fp = open("config-%s" % target_arch)
542 except:
543     print "Unable to open config file 'config-%s'" % target_arch
544     sys.exit(1)
545
546 #
547 # Open and prepare output files.
548 #
549 try:
550     c_fp = open("%s/InterpC-%s.cpp" % (output_dir, target_arch), "w")
551     asm_fp = open("%s/InterpAsm-%s.S" % (output_dir, target_arch), "w")
552 except:
553     print "Unable to open output files"
554     print "Make sure directory '%s' exists and existing files are writable" \
555             % output_dir
556     # Ideally we'd remove the files to avoid confusing "make", but if they
557     # failed to open we probably won't be able to remove them either.
558     sys.exit(1)
559
560 print "Generating %s, %s" % (c_fp.name, asm_fp.name)
561
562 file_header = """/*
563  * This file was generated automatically by gen-mterp.py for '%s'.
564  *
565  * --> DO NOT EDIT <--
566  */
567
568 """ % (target_arch)
569
570 c_fp.write(file_header)
571 asm_fp.write(file_header)
572
573 #
574 # Process the config file.
575 #
576 failed = False
577 try:
578     for line in config_fp:
579         line = line.strip()         # remove CRLF, leading spaces
580         tokens = line.split(' ')    # tokenize
581         #print "%d: %s" % (len(tokens), tokens)
582         if len(tokens[0]) == 0:
583             #print "  blank"
584             pass
585         elif tokens[0][0] == '#':
586             #print "  comment"
587             pass
588         else:
589             if tokens[0] == "handler-size":
590                 setHandlerSize(tokens)
591             elif tokens[0] == "import":
592                 importFile(tokens)
593             elif tokens[0] == "asm-stub":
594                 setAsmStub(tokens)
595             elif tokens[0] == "asm-alt-stub":
596                 setAsmAltStub(tokens)
597             elif tokens[0] == "op-start":
598                 opStart(tokens)
599             elif tokens[0] == "op-end":
600                 opEnd(tokens)
601             elif tokens[0] == "alt":
602                 altEntry(tokens)
603             elif tokens[0] == "op":
604                 opEntry(tokens)
605             elif tokens[0] == "handler-style":
606                 setHandlerStyle(tokens)
607             elif tokens[0] == "alt-ops":
608                 genaltop(tokens)
609             elif tokens[0] == "split-ops":
610                 splitops = True
611             else:
612                 raise DataParseError, "unrecognized command '%s'" % tokens[0]
613             if style == None:
614                 print "tokens[0] = %s" % tokens[0]
615                 raise DataParseError, "handler-style must be first command"
616 except DataParseError, err:
617     print "Failed: " + str(err)
618     # TODO: remove output files so "make" doesn't get confused
619     failed = True
620     c_fp.close()
621     asm_fp.close()
622     c_fp = asm_fp = None
623
624 config_fp.close()
625
626 #
627 # Done!
628 #
629 if c_fp:
630     c_fp.close()
631 if asm_fp:
632     asm_fp.close()
633
634 sys.exit(failed)