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