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