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