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