3 # Copyright (C) 2007 The Android Open Source Project
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 # Using instructions from an architecture-specific config file, generate C
19 # and assembly source files for the Dalvik interpreter.
22 import sys, string, re, time
23 from string import Template
25 interp_defs_file = "../../libdex/DexOpcodes.h" # need opcode list
26 kNumPackedOpcodes = 512 # TODO: Derive this from DexOpcodes.h.
29 handler_size_bits = -1000
30 handler_size_bytes = -1000
31 in_op_start = 0 # 0=not started, 1=started, 2=ended
35 label_prefix = ".L" # use ".L" to hide labels from gdb
38 class DataParseError(SyntaxError):
39 "Failure when parsing data file"
42 # Set any omnipresent substitution values.
44 def getGlobalSubDict():
45 return { "handler_size_bits":handler_size_bits,
46 "handler_size_bytes":handler_size_bytes }
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
54 def setHandlerSize(tokens):
55 global handler_size_bits, handler_size_bytes
57 raise DataParseError("handler-size requires one argument")
58 if handler_size_bits != -1000:
59 raise DataParseError("handler-size may only be set once")
61 # compute log2(n), and make sure n is a power of 2
62 handler_size_bytes = bytes = int(tokens[1])
65 bytes //= 2 # halve with truncating division
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" \
71 handler_size_bits = bits
74 # Parse arch config file --
75 # Copy a file in to the C or asm output file.
77 def importFile(tokens):
79 raise DataParseError("import requires one argument")
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)
86 raise DataParseError("don't know how to import %s (expecting .c/.S)"
90 # Parse arch config file --
91 # Copy a file in to the C or asm output file.
93 def setAsmStub(tokens):
96 raise DataParseError("import requires one argument")
98 stub_fp = open(tokens[1])
99 asm_stub_text = stub_fp.readlines()
102 raise DataParseError("unable to load asm-stub: %s" % str(err))
106 # Parse arch config file --
107 # Start of opcode list.
111 global default_op_dir
113 raise DataParseError("opStart takes a directory name argument")
115 raise DataParseError("opStart can only be specified once")
116 default_op_dir = tokens[1]
120 # Parse arch config file --
121 # Set location of a single opcode's source file.
124 #global opcode_locations
126 raise DataParseError("op requires exactly two arguments")
128 raise DataParseError("op statements must be between opStart/opEnd")
130 index = opcodes.index(tokens[1])
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]
139 # Parse arch config file --
140 # End of opcode list; emit instruction blocks.
145 raise DataParseError("opEnd takes no arguments")
147 raise DataParseError("opEnd must follow opStart, and only appear once")
154 # Extract an ordered list of instructions from the VM sources. We use the
155 # "goto table" definition macro, which has exactly kNumPackedOpcodes
160 opcode_fp = open(interp_defs_file)
161 opcode_re = re.compile(r"^\s*H\(OP_(\w+)\),.*", re.DOTALL)
162 for line in opcode_fp:
163 match = opcode_re.match(line)
166 opcodes.append("OP_" + match.group(1))
169 if len(opcodes) != kNumPackedOpcodes:
170 print "ERROR: found %d opcodes in Interp.h (expected %d)" \
171 % (len(opcodes), kNumPackedOpcodes)
172 raise SyntaxError, "bad opcode count"
177 # Load and emit opcodes for all kNumPackedOpcodes instructions.
179 def loadAndEmitOpcodes():
181 assert len(opcodes) == kNumPackedOpcodes
182 need_dummy_start = False
184 # point dvmAsmInstructionStart at the first handler or stub
185 asm_fp.write("\n .global dvmAsmInstructionStart\n")
186 asm_fp.write(" .type dvmAsmInstructionStart, %function\n")
187 asm_fp.write("dvmAsmInstructionStart = " + label_prefix + "_OP_NOP\n")
188 asm_fp.write(" .text\n\n")
190 for i in xrange(kNumPackedOpcodes):
193 if opcode_locations.has_key(op):
194 location = opcode_locations[op]
196 location = default_op_dir
199 loadAndEmitC(location, i)
200 if len(asm_stub_text) == 0:
201 need_dummy_start = True
203 loadAndEmitAsm(location, i, sister_list)
205 # For a 100% C implementation, there are no asm handlers or stubs. We
206 # need to have the dvmAsmInstructionStart label point at OP_NOP, and it's
207 # too annoying to try to slide it in after the alignment psuedo-op, so
208 # we take the low road and just emit a dummy OP_NOP here.
210 # The dvmAsmInstructionStart will also use the size for OP_NOP, so
211 # we want to generate a .size directive that spans all handlers.
213 asm_fp.write(" .balign %d\n" % handler_size_bytes)
214 asm_fp.write(label_prefix + "_OP_NOP: /* dummy */\n");
216 asm_fp.write("\n .balign %d\n" % handler_size_bytes)
217 asm_fp.write(" .size .L_OP_NOP, .-.L_OP_NOP\n")
218 asm_fp.write(" .global dvmAsmInstructionEnd\n")
219 asm_fp.write("dvmAsmInstructionEnd:\n")
221 emitSectionComment("Sister implementations", asm_fp)
222 asm_fp.write(" .global dvmAsmSisterStart\n")
223 asm_fp.write(" .type dvmAsmSisterStart, %function\n")
224 asm_fp.write(" .text\n")
225 asm_fp.write(" .balign 4\n")
226 asm_fp.write("dvmAsmSisterStart:\n")
227 asm_fp.writelines(sister_list)
229 asm_fp.write("\n .size dvmAsmSisterStart, .-dvmAsmSisterStart\n")
230 asm_fp.write(" .global dvmAsmSisterEnd\n")
231 asm_fp.write("dvmAsmSisterEnd:\n\n")
234 # Load a C fragment and emit it, then output an assembly stub.
236 def loadAndEmitC(location, opindex):
237 op = opcodes[opindex]
238 source = "%s/%s.c" % (location, op)
240 print " emit %s --> C" % source
241 dict = getGlobalSubDict()
242 dict.update({ "opcode":op, "opnum":opindex })
244 appendSourceFile(source, dict, c_fp, None)
246 if len(asm_stub_text) != 0:
247 emitAsmStub(asm_fp, dict)
250 # Load an assembly fragment and emit it.
252 def loadAndEmitAsm(location, opindex, sister_list):
253 op = opcodes[opindex]
254 source = "%s/%s.S" % (location, op)
255 dict = getGlobalSubDict()
256 dict.update({ "opcode":op, "opnum":opindex })
258 print " emit %s --> asm" % source
260 emitAsmHeader(asm_fp, dict)
261 appendSourceFile(source, dict, asm_fp, sister_list)
264 # Output the alignment directive and label for an assembly piece.
266 def emitAsmHeader(outfp, dict):
267 outfp.write("/* ------------------------------ */\n")
268 # The alignment directive ensures that the handler occupies
269 # at least the correct amount of space. We don't try to deal
270 # with overflow here.
271 outfp.write(" .balign %d\n" % handler_size_bytes)
272 # Emit a label so that gdb will say the right thing. We prepend an
273 # underscore so the symbol name doesn't clash with the Opcode enum.
274 outfp.write(label_prefix + "_%(opcode)s: /* 0x%(opnum)02x */\n" % dict)
277 # Output a generic instruction stub that updates the "glue" struct and
278 # calls the C implementation.
280 def emitAsmStub(outfp, dict):
281 emitAsmHeader(outfp, dict)
282 for line in asm_stub_text:
283 templ = Template(line)
284 outfp.write(templ.substitute(dict))
287 # Append the file specified by "source" to the open "outfp". Each line will
288 # be template-replaced using the substitution dictionary "dict".
290 # If the first line of the file starts with "%" it is taken as a directive.
291 # A "%include" line contains a filename and, optionally, a Python-style
292 # dictionary declaration with substitution strings. (This is implemented
295 # If "sister_list" is provided, and we find a line that contains only "&",
296 # all subsequent lines from the file will be appended to sister_list instead
297 # of copied to the output.
299 # This may modify "dict".
301 def appendSourceFile(source, dict, outfp, sister_list):
302 outfp.write("/* File: %s */\n" % source)
303 infp = open(source, "r")
306 if line.startswith("%include"):
307 # Parse the "include" line
308 tokens = line.strip().split(' ', 2)
310 raise DataParseError("malformed %%include in %s" % source)
312 alt_source = tokens[1].strip("\"")
313 if alt_source == source:
314 raise DataParseError("self-referential %%include in %s"
317 new_dict = dict.copy()
319 new_dict.update(eval(tokens[2]))
320 #print " including src=%s dict=%s" % (alt_source, new_dict)
321 appendSourceFile(alt_source, new_dict, outfp, sister_list)
324 elif line.startswith("%default"):
325 # copy keywords into dictionary
326 tokens = line.strip().split(' ', 1)
328 raise DataParseError("malformed %%default in %s" % source)
329 defaultValues = eval(tokens[1])
330 for entry in defaultValues:
331 dict.setdefault(entry, defaultValues[entry])
334 elif line.startswith("%verify"):
335 # more to come, someday
338 elif line.startswith("%break") and sister_list != None:
339 # allow more than one %break, ignoring all following the first
342 sister_list.append("\n/* continuation for %(opcode)s */\n"%dict)
345 # perform keyword substitution if a dictionary was provided
347 templ = Template(line)
349 subline = templ.substitute(dict)
350 except KeyError, err:
351 raise DataParseError("keyword substitution failed in %s: %s"
352 % (source, str(err)))
354 print "ERROR: substitution failed: " + line
359 # write output to appropriate file
361 sister_list.append(subline)
368 # Emit a C-style section header comment.
370 def emitSectionComment(str, fp):
371 equals = "========================================" \
372 "==================================="
374 fp.write("\n/*\n * %s\n * %s\n * %s\n */\n" %
375 (equals, str, equals))
379 # ===========================================================================
386 if len(sys.argv) != 3:
387 print "Usage: %s target-arch output-dir" % sys.argv[0]
390 target_arch = sys.argv[1]
391 output_dir = sys.argv[2]
394 # Extract opcode list.
396 opcodes = getOpcodeList()
404 config_fp = open("config-%s" % target_arch)
406 print "Unable to open config file 'config-%s'" % target_arch
410 # Open and prepare output files.
413 c_fp = open("%s/InterpC-%s.c" % (output_dir, target_arch), "w")
414 asm_fp = open("%s/InterpAsm-%s.S" % (output_dir, target_arch), "w")
416 print "Unable to open output files"
417 print "Make sure directory '%s' exists and existing files are writable" \
419 # Ideally we'd remove the files to avoid confusing "make", but if they
420 # failed to open we probably won't be able to remove them either.
423 print "Generating %s, %s" % (c_fp.name, asm_fp.name)
426 * This file was generated automatically by gen-mterp.py for '%s'.
428 * --> DO NOT EDIT <--
433 c_fp.write(file_header)
434 asm_fp.write(file_header)
437 # Process the config file.
441 for line in config_fp:
442 line = line.strip() # remove CRLF, leading spaces
443 tokens = line.split(' ') # tokenize
444 #print "%d: %s" % (len(tokens), tokens)
445 if len(tokens[0]) == 0:
448 elif tokens[0][0] == '#':
452 if tokens[0] == "handler-size":
453 setHandlerSize(tokens)
454 elif tokens[0] == "import":
456 elif tokens[0] == "asm-stub":
458 elif tokens[0] == "op-start":
460 elif tokens[0] == "op-end":
462 elif tokens[0] == "op":
465 raise DataParseError, "unrecognized command '%s'" % tokens[0]
466 except DataParseError, err:
467 print "Failed: " + str(err)
468 # TODO: remove output files so "make" doesn't get confused