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