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