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