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