OSDN Git Service

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