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 22import sys, string, re, time 23from string import Template 24 25interp_defs_file = "../../../libdexfile/dex/dex_instruction_list.h" # need opcode list 26kNumPackedOpcodes = 256 27 28splitops = False 29verbose = False 30handler_size_bits = -1000 31handler_size_bytes = -1000 32in_op_start = 0 # 0=not started, 1=started, 2=ended 33in_alt_op_start = 0 # 0=not started, 1=started, 2=ended 34default_op_dir = None 35default_alt_stub = None 36opcode_locations = {} 37alt_opcode_locations = {} 38asm_stub_text = [] 39fallback_stub_text = [] 40label_prefix = ".L" # use ".L" to hide labels from gdb 41alt_label_prefix = ".L_ALT" # use ".L" to hide labels from gdb 42style = None # interpreter style 43generate_alt_table = False 44function_type_format = ".type %s, %%function" 45function_size_format = ".size %s, .-%s" 46global_name_format = "%s" 47 48# Exception class. 49class DataParseError(SyntaxError): 50 "Failure when parsing data file" 51 52# 53# Set any omnipresent substitution values. 54# 55def 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# 63def 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# 77def 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# 102def 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# 116def 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# 132def 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# 147def 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# 156def setFunctionTypeFormat(tokens): 157 global function_type_format 158 function_type_format = tokens[1] 159# 160# Change the default function size format 161# 162def setFunctionSizeFormat(tokens): 163 global function_size_format 164 function_size_format = tokens[1] 165# 166# Change the global name format 167# 168def 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# 175def 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# 189def 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# 209def 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# 228def 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 241def 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# 254def 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 271def 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# 278def 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("%s = " % start_label + label_prefix + "_op_nop\n") 288 asm_fp.write(" .text\n\n") 289 290 for i in xrange(kNumPackedOpcodes): 291 op = opcodes[i] 292 293 if opcode_locations.has_key(op): 294 location = opcode_locations[op] 295 else: 296 location = default_op_dir 297 298 if location == "FALLBACK": 299 emitFallback(i) 300 else: 301 loadAndEmitAsm(location, i, sister_list) 302 303 # For a 100% C implementation, there are no asm handlers or stubs. We 304 # need to have the MterpAsmInstructionStart label point at op_nop, and it's 305 # too annoying to try to slide it in after the alignment psuedo-op, so 306 # we take the low road and just emit a dummy op_nop here. 307 if need_dummy_start: 308 emitAlign() 309 asm_fp.write(label_prefix + "_op_nop: /* dummy */\n"); 310 311 emitAlign() 312 asm_fp.write(" .global %s\n" % end_label) 313 asm_fp.write("%s:\n" % end_label) 314 315 if style == "computed-goto": 316 start_sister_label = global_name_format % "artMterpAsmSisterStart" 317 end_sister_label = global_name_format % "artMterpAsmSisterEnd" 318 emitSectionComment("Sister implementations", asm_fp) 319 asm_fp.write(" .global %s\n" % start_sister_label) 320 asm_fp.write(" .text\n") 321 asm_fp.write(" .balign 4\n") 322 asm_fp.write("%s:\n" % start_sister_label) 323 asm_fp.writelines(sister_list) 324 asm_fp.write(" .global %s\n" % end_sister_label) 325 asm_fp.write("%s:\n\n" % end_sister_label) 326 327# 328# Load an alternate entry stub 329# 330def loadAndEmitAltStub(source, opindex): 331 op = opcodes[opindex] 332 if verbose: 333 print " alt emit %s --> stub" % source 334 dict = getGlobalSubDict() 335 dict.update({ "opcode":op, "opnum":opindex }) 336 337 emitAsmHeader(asm_fp, dict, alt_label_prefix) 338 appendSourceFile(source, dict, asm_fp, None) 339 340# 341# Load and emit alternate opcodes for all kNumPackedOpcodes instructions. 342# 343def loadAndEmitAltOpcodes(): 344 assert len(opcodes) == kNumPackedOpcodes 345 start_label = global_name_format % "artMterpAsmAltInstructionStart" 346 end_label = global_name_format % "artMterpAsmAltInstructionEnd" 347 348 # point MterpAsmInstructionStart at the first handler or stub 349 asm_fp.write("\n .global %s\n" % start_label) 350 asm_fp.write(" .text\n\n") 351 asm_fp.write("%s = " % start_label + label_prefix + "_ALT_op_nop\n") 352 353 for i in xrange(kNumPackedOpcodes): 354 op = opcodes[i] 355 if alt_opcode_locations.has_key(op): 356 source = "%s/alt_%s.S" % (alt_opcode_locations[op], op) 357 else: 358 source = default_alt_stub 359 loadAndEmitAltStub(source, i) 360 361 emitAlign() 362 asm_fp.write(" .global %s\n" % end_label) 363 asm_fp.write("%s:\n" % end_label) 364 365# 366# Load an assembly fragment and emit it. 367# 368def loadAndEmitAsm(location, opindex, sister_list): 369 op = opcodes[opindex] 370 source = "%s/%s.S" % (location, op) 371 dict = getGlobalSubDict() 372 dict.update({ "opcode":op, "opnum":opindex }) 373 if verbose: 374 print " emit %s --> asm" % source 375 376 emitAsmHeader(asm_fp, dict, label_prefix) 377 appendSourceFile(source, dict, asm_fp, sister_list) 378 379# 380# Emit fallback fragment 381# 382def emitFallback(opindex): 383 op = opcodes[opindex] 384 dict = getGlobalSubDict() 385 dict.update({ "opcode":op, "opnum":opindex }) 386 emitAsmHeader(asm_fp, dict, label_prefix) 387 for line in fallback_stub_text: 388 asm_fp.write(line) 389 asm_fp.write("\n") 390 391# 392# Output the alignment directive and label for an assembly piece. 393# 394def emitAsmHeader(outfp, dict, prefix): 395 outfp.write("/* ------------------------------ */\n") 396 # The alignment directive ensures that the handler occupies 397 # at least the correct amount of space. We don't try to deal 398 # with overflow here. 399 emitAlign() 400 # Emit a label so that gdb will say the right thing. We prepend an 401 # underscore so the symbol name doesn't clash with the Opcode enum. 402 outfp.write(prefix + "_%(opcode)s: /* 0x%(opnum)02x */\n" % dict) 403 404# 405# Output a generic instruction stub that updates the "glue" struct and 406# calls the C implementation. 407# 408def emitAsmStub(outfp, dict): 409 emitAsmHeader(outfp, dict, label_prefix) 410 for line in asm_stub_text: 411 templ = Template(line) 412 outfp.write(templ.substitute(dict)) 413 414# 415# Append the file specified by "source" to the open "outfp". Each line will 416# be template-replaced using the substitution dictionary "dict". 417# 418# If the first line of the file starts with "%" it is taken as a directive. 419# A "%include" line contains a filename and, optionally, a Python-style 420# dictionary declaration with substitution strings. (This is implemented 421# with recursion.) 422# 423# If "sister_list" is provided, and we find a line that contains only "&", 424# all subsequent lines from the file will be appended to sister_list instead 425# of copied to the output. 426# 427# This may modify "dict". 428# 429def appendSourceFile(source, dict, outfp, sister_list): 430 outfp.write("/* File: %s */\n" % source) 431 infp = open(source, "r") 432 in_sister = False 433 for line in infp: 434 if line.startswith("%include"): 435 # Parse the "include" line 436 tokens = line.strip().split(' ', 2) 437 if len(tokens) < 2: 438 raise DataParseError("malformed %%include in %s" % source) 439 440 alt_source = tokens[1].strip("\"") 441 if alt_source == source: 442 raise DataParseError("self-referential %%include in %s" 443 % source) 444 445 new_dict = dict.copy() 446 if len(tokens) == 3: 447 new_dict.update(eval(tokens[2])) 448 #print " including src=%s dict=%s" % (alt_source, new_dict) 449 appendSourceFile(alt_source, new_dict, outfp, sister_list) 450 continue 451 452 elif line.startswith("%default"): 453 # copy keywords into dictionary 454 tokens = line.strip().split(' ', 1) 455 if len(tokens) < 2: 456 raise DataParseError("malformed %%default in %s" % source) 457 defaultValues = eval(tokens[1]) 458 for entry in defaultValues: 459 dict.setdefault(entry, defaultValues[entry]) 460 continue 461 462 elif line.startswith("%break") and sister_list != None: 463 # allow more than one %break, ignoring all following the first 464 if style == "computed-goto" and not in_sister: 465 in_sister = True 466 sister_list.append("\n/* continuation for %(opcode)s */\n"%dict) 467 continue 468 469 # perform keyword substitution if a dictionary was provided 470 if dict != None: 471 templ = Template(line) 472 try: 473 subline = templ.substitute(dict) 474 except KeyError, err: 475 raise DataParseError("keyword substitution failed in %s: %s" 476 % (source, str(err))) 477 except: 478 print "ERROR: substitution failed: " + line 479 raise 480 else: 481 subline = line 482 483 # write output to appropriate file 484 if in_sister: 485 sister_list.append(subline) 486 else: 487 outfp.write(subline) 488 outfp.write("\n") 489 infp.close() 490 491# 492# Emit a C-style section header comment. 493# 494def emitSectionComment(str, fp): 495 equals = "========================================" \ 496 "===================================" 497 498 fp.write("\n/*\n * %s\n * %s\n * %s\n */\n" % 499 (equals, str, equals)) 500 501 502# 503# =========================================================================== 504# "main" code 505# 506 507# 508# Check args. 509# 510if len(sys.argv) != 3: 511 print "Usage: %s target-arch output-dir" % sys.argv[0] 512 sys.exit(2) 513 514target_arch = sys.argv[1] 515output_dir = sys.argv[2] 516 517# 518# Extract opcode list. 519# 520opcodes = getOpcodeList() 521#for op in opcodes: 522# print " %s" % op 523 524# 525# Open config file. 526# 527try: 528 config_fp = open("config_%s" % target_arch) 529except: 530 print "Unable to open config file 'config_%s'" % target_arch 531 sys.exit(1) 532 533# 534# Open and prepare output files. 535# 536try: 537 asm_fp = open("%s/mterp_%s.S" % (output_dir, target_arch), "w") 538except: 539 print "Unable to open output files" 540 print "Make sure directory '%s' exists and existing files are writable" \ 541 % output_dir 542 # Ideally we'd remove the files to avoid confusing "make", but if they 543 # failed to open we probably won't be able to remove them either. 544 sys.exit(1) 545 546print "Generating %s" % (asm_fp.name) 547 548file_header = """/* 549 * This file was generated automatically by gen-mterp.py for '%s'. 550 * 551 * --> DO NOT EDIT <-- 552 */ 553 554""" % (target_arch) 555 556asm_fp.write(file_header) 557 558# 559# Process the config file. 560# 561failed = False 562try: 563 for line in config_fp: 564 line = line.strip() # remove CRLF, leading spaces 565 tokens = line.split(' ') # tokenize 566 #print "%d: %s" % (len(tokens), tokens) 567 if len(tokens[0]) == 0: 568 #print " blank" 569 pass 570 elif tokens[0][0] == '#': 571 #print " comment" 572 pass 573 else: 574 if tokens[0] == "handler-size": 575 setHandlerSize(tokens) 576 elif tokens[0] == "import": 577 importFile(tokens) 578 elif tokens[0] == "asm-stub": 579 setAsmStub(tokens) 580 elif tokens[0] == "asm-alt-stub": 581 setAsmAltStub(tokens) 582 elif tokens[0] == "op-start": 583 opStart(tokens) 584 elif tokens[0] == "op-end": 585 opEnd(tokens) 586 elif tokens[0] == "alt": 587 altEntry(tokens) 588 elif tokens[0] == "op": 589 opEntry(tokens) 590 elif tokens[0] == "handler-style": 591 setHandlerStyle(tokens) 592 elif tokens[0] == "alt-ops": 593 genaltop(tokens) 594 elif tokens[0] == "split-ops": 595 splitops = True 596 elif tokens[0] == "fallback-stub": 597 setFallbackStub(tokens) 598 elif tokens[0] == "function-type-format": 599 setFunctionTypeFormat(tokens) 600 elif tokens[0] == "function-size-format": 601 setFunctionSizeFormat(tokens) 602 elif tokens[0] == "global-name-format": 603 setGlobalNameFormat(tokens) 604 else: 605 raise DataParseError, "unrecognized command '%s'" % tokens[0] 606 if style == None: 607 print "tokens[0] = %s" % tokens[0] 608 raise DataParseError, "handler-style must be first command" 609except DataParseError, err: 610 print "Failed: " + str(err) 611 # TODO: remove output files so "make" doesn't get confused 612 failed = True 613 asm_fp.close() 614 asm_fp = None 615 616config_fp.close() 617 618# 619# Done! 620# 621if asm_fp: 622 asm_fp.close() 623 624sys.exit(failed) 625