Home | History | Annotate | Download | only in template
      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 JIT.
     20 #
     21 
     22 import sys, string, re, time
     23 from string import Template
     24 
     25 interp_defs_file = "TemplateOpList.h" # need opcode list
     26 
     27 handler_size_bits = -1000
     28 handler_size_bytes = -1000
     29 in_op_start = 0             # 0=not started, 1=started, 2=ended
     30 default_op_dir = None
     31 opcode_locations = {}
     32 asm_stub_text = []
     33 label_prefix = ".L"         # use ".L" to hide labels from gdb
     34 
     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(".S"):
     81         appendSourceFile(tokens[1], getGlobalSubDict(), asm_fp, None)
     82     else:
     83         raise DataParseError("don't know how to import %s (expecting .c/.S)"
     84                 % source)
     85 
     86 #
     87 # Parse arch config file --
     88 # Copy a file in to the C or asm output file.
     89 #
     90 def setAsmStub(tokens):
     91     global asm_stub_text
     92     if len(tokens) != 2:
     93         raise DataParseError("import requires one argument")
     94     try:
     95         stub_fp = open(tokens[1])
     96         asm_stub_text = stub_fp.readlines()
     97     except IOError, err:
     98         stub_fp.close()
     99         raise DataParseError("unable to load asm-stub: %s" % str(err))
    100     stub_fp.close()
    101 
    102 #
    103 # Parse arch config file --
    104 # Start of opcode list.
    105 #
    106 def opStart(tokens):
    107     global in_op_start
    108     global default_op_dir
    109     if len(tokens) != 2:
    110         raise DataParseError("opStart takes a directory name argument")
    111     if in_op_start != 0:
    112         raise DataParseError("opStart can only be specified once")
    113     default_op_dir = tokens[1]
    114     in_op_start = 1
    115 
    116 #
    117 # Parse arch config file --
    118 # Set location of a single opcode's source file.
    119 #
    120 def opEntry(tokens):
    121     #global opcode_locations
    122     if len(tokens) != 3:
    123         raise DataParseError("op requires exactly two arguments")
    124     if in_op_start != 1:
    125         raise DataParseError("op statements must be between opStart/opEnd")
    126     try:
    127         index = opcodes.index(tokens[1])
    128     except ValueError:
    129         raise DataParseError("unknown opcode %s" % tokens[1])
    130     opcode_locations[tokens[1]] = tokens[2]
    131 
    132 #
    133 # Parse arch config file --
    134 # End of opcode list; emit instruction blocks.
    135 #
    136 def opEnd(tokens):
    137     global in_op_start
    138     if len(tokens) != 1:
    139         raise DataParseError("opEnd takes no arguments")
    140     if in_op_start != 1:
    141         raise DataParseError("opEnd must follow opStart, and only appear once")
    142     in_op_start = 2
    143 
    144     loadAndEmitOpcodes()
    145 
    146 
    147 #
    148 # Extract an ordered list of instructions from the VM sources.  We use the
    149 # "goto table" definition macro, which has exactly 256 entries.
    150 #
    151 def getOpcodeList():
    152     opcodes = []
    153     opcode_fp = open("%s/%s" % (target_arch, interp_defs_file))
    154     opcode_re = re.compile(r"^JIT_TEMPLATE\((\w+)\)", re.DOTALL)
    155     for line in opcode_fp:
    156         match = opcode_re.match(line)
    157         if not match:
    158             continue
    159         opcodes.append("TEMPLATE_" + match.group(1))
    160     opcode_fp.close()
    161 
    162     return opcodes
    163 
    164 
    165 #
    166 # Load and emit opcodes for all 256 instructions.
    167 #
    168 def loadAndEmitOpcodes():
    169     sister_list = []
    170 
    171     # point dvmAsmInstructionStart at the first handler or stub
    172     asm_fp.write("\n    .global dvmCompilerTemplateStart\n")
    173     asm_fp.write("    .type   dvmCompilerTemplateStart, %function\n")
    174     asm_fp.write("    .text\n\n")
    175     asm_fp.write("dvmCompilerTemplateStart:\n\n")
    176 
    177     for i in xrange(len(opcodes)):
    178         op = opcodes[i]
    179 
    180         if opcode_locations.has_key(op):
    181             location = opcode_locations[op]
    182         else:
    183             location = default_op_dir
    184 
    185         loadAndEmitAsm(location, i, sister_list)
    186 
    187     # Use variable sized handlers now
    188     # asm_fp.write("\n    .balign %d\n" % handler_size_bytes)
    189     asm_fp.write("    .size   dvmCompilerTemplateStart, .-dvmCompilerTemplateStart\n")
    190 
    191 #
    192 # Load an assembly fragment and emit it.
    193 #
    194 def loadAndEmitAsm(location, opindex, sister_list):
    195     op = opcodes[opindex]
    196     source = "%s/%s.S" % (location, op)
    197     dict = getGlobalSubDict()
    198     dict.update({ "opcode":op, "opnum":opindex })
    199     print " emit %s --> asm" % source
    200 
    201     emitAsmHeader(asm_fp, dict)
    202     appendSourceFile(source, dict, asm_fp, sister_list)
    203 
    204 #
    205 # Output the alignment directive and label for an assembly piece.
    206 #
    207 def emitAsmHeader(outfp, dict):
    208     outfp.write("/* ------------------------------ */\n")
    209     # The alignment directive ensures that the handler occupies
    210     # at least the correct amount of space.  We don't try to deal
    211     # with overflow here.
    212     outfp.write("    .balign 4\n")
    213     # Emit a label so that gdb will say the right thing.  We prepend an
    214     # underscore so the symbol name doesn't clash with the OpCode enum.
    215     template_name = "dvmCompiler_%(opcode)s" % dict
    216     outfp.write("    .global %s\n" % template_name);
    217     outfp.write("%s:\n" % template_name);
    218 
    219 #
    220 # Output a generic instruction stub that updates the "glue" struct and
    221 # calls the C implementation.
    222 #
    223 def emitAsmStub(outfp, dict):
    224     emitAsmHeader(outfp, dict)
    225     for line in asm_stub_text:
    226         templ = Template(line)
    227         outfp.write(templ.substitute(dict))
    228 
    229 #
    230 # Append the file specified by "source" to the open "outfp".  Each line will
    231 # be template-replaced using the substitution dictionary "dict".
    232 #
    233 # If the first line of the file starts with "%" it is taken as a directive.
    234 # A "%include" line contains a filename and, optionally, a Python-style
    235 # dictionary declaration with substitution strings.  (This is implemented
    236 # with recursion.)
    237 #
    238 # If "sister_list" is provided, and we find a line that contains only "&",
    239 # all subsequent lines from the file will be appended to sister_list instead
    240 # of copied to the output.
    241 #
    242 # This may modify "dict".
    243 #
    244 def appendSourceFile(source, dict, outfp, sister_list):
    245     outfp.write("/* File: %s */\n" % source)
    246     infp = open(source, "r")
    247     in_sister = False
    248     for line in infp:
    249         if line.startswith("%include"):
    250             # Parse the "include" line
    251             tokens = line.strip().split(' ', 2)
    252             if len(tokens) < 2:
    253                 raise DataParseError("malformed %%include in %s" % source)
    254 
    255             alt_source = tokens[1].strip("\"")
    256             if alt_source == source:
    257                 raise DataParseError("self-referential %%include in %s"
    258                         % source)
    259 
    260             new_dict = dict.copy()
    261             if len(tokens) == 3:
    262                 new_dict.update(eval(tokens[2]))
    263             #print " including src=%s dict=%s" % (alt_source, new_dict)
    264             appendSourceFile(alt_source, new_dict, outfp, sister_list)
    265             continue
    266 
    267         elif line.startswith("%default"):
    268             # copy keywords into dictionary
    269             tokens = line.strip().split(' ', 1)
    270             if len(tokens) < 2:
    271                 raise DataParseError("malformed %%default in %s" % source)
    272             defaultValues = eval(tokens[1])
    273             for entry in defaultValues:
    274                 dict.setdefault(entry, defaultValues[entry])
    275             continue
    276 
    277         elif line.startswith("%verify"):
    278             # more to come, someday
    279             continue
    280 
    281         elif line.startswith("%break") and sister_list != None:
    282             # allow more than one %break, ignoring all following the first
    283             if not in_sister:
    284                 in_sister = True
    285                 sister_list.append("\n/* continuation for %(opcode)s */\n"%dict)
    286             continue
    287 
    288         # perform keyword substitution if a dictionary was provided
    289         if dict != None:
    290             templ = Template(line)
    291             try:
    292                 subline = templ.substitute(dict)
    293             except KeyError, err:
    294                 raise DataParseError("keyword substitution failed in %s: %s"
    295                         % (source, str(err)))
    296             except:
    297                 print "ERROR: substitution failed: " + line
    298                 raise
    299         else:
    300             subline = line
    301 
    302         # write output to appropriate file
    303         if in_sister:
    304             sister_list.append(subline)
    305         else:
    306             outfp.write(subline)
    307     outfp.write("\n")
    308     infp.close()
    309 
    310 #
    311 # Emit a C-style section header comment.
    312 #
    313 def emitSectionComment(str, fp):
    314     equals = "========================================" \
    315              "==================================="
    316 
    317     fp.write("\n/*\n * %s\n *  %s\n * %s\n */\n" %
    318         (equals, str, equals))
    319 
    320 
    321 #
    322 # ===========================================================================
    323 # "main" code
    324 #
    325 
    326 #
    327 # Check args.
    328 #
    329 if len(sys.argv) != 3:
    330     print "Usage: %s target-arch output-dir" % sys.argv[0]
    331     sys.exit(2)
    332 
    333 target_arch = sys.argv[1]
    334 output_dir = sys.argv[2]
    335 
    336 #
    337 # Extract opcode list.
    338 #
    339 opcodes = getOpcodeList()
    340 #for op in opcodes:
    341 #    print "  %s" % op
    342 
    343 #
    344 # Open config file.
    345 #
    346 try:
    347     config_fp = open("config-%s" % target_arch)
    348 except:
    349     print "Unable to open config file 'config-%s'" % target_arch
    350     sys.exit(1)
    351 
    352 #
    353 # Open and prepare output files.
    354 #
    355 try:
    356     asm_fp = open("%s/CompilerTemplateAsm-%s.S" % (output_dir, target_arch), "w")
    357 except:
    358     print "Unable to open output files"
    359     print "Make sure directory '%s' exists and existing files are writable" \
    360             % output_dir
    361     # Ideally we'd remove the files to avoid confusing "make", but if they
    362     # failed to open we probably won't be able to remove them either.
    363     sys.exit(1)
    364 
    365 print "Generating %s" % (asm_fp.name)
    366 
    367 file_header = """/*
    368  * This file was generated automatically by gen-template.py for '%s'.
    369  *
    370  * --> DO NOT EDIT <--
    371  */
    372 
    373 """ % (target_arch)
    374 
    375 asm_fp.write(file_header)
    376 
    377 #
    378 # Process the config file.
    379 #
    380 failed = False
    381 try:
    382     for line in config_fp:
    383         line = line.strip()         # remove CRLF, leading spaces
    384         tokens = line.split(' ')    # tokenize
    385         #print "%d: %s" % (len(tokens), tokens)
    386         if len(tokens[0]) == 0:
    387             #print "  blank"
    388             pass
    389         elif tokens[0][0] == '#':
    390             #print "  comment"
    391             pass
    392         else:
    393             if tokens[0] == "handler-size":
    394                 setHandlerSize(tokens)
    395             elif tokens[0] == "import":
    396                 importFile(tokens)
    397             elif tokens[0] == "asm-stub":
    398                 setAsmStub(tokens)
    399             elif tokens[0] == "op-start":
    400                 opStart(tokens)
    401             elif tokens[0] == "op-end":
    402                 opEnd(tokens)
    403             elif tokens[0] == "op":
    404                 opEntry(tokens)
    405             else:
    406                 raise DataParseError, "unrecognized command '%s'" % tokens[0]
    407 except DataParseError, err:
    408     print "Failed: " + str(err)
    409     # TODO: remove output files so "make" doesn't get confused
    410     failed = True
    411     asm_fp.close()
    412     c_fp = asm_fp = None
    413 
    414 config_fp.close()
    415 
    416 #
    417 # Done!
    418 #
    419 if asm_fp:
    420     asm_fp.close()
    421 
    422 sys.exit(failed)
    423