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