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