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.
30 handler_size_bits = -1000
31 handler_size_bytes = -1000
32 in_op_start = 0 # 0=not started, 1=started, 2=ended
33 in_alt_op_start = 0 # 0=not started, 1=started, 2=ended
35 default_alt_stub = None
37 alt_opcode_locations = {}
39 label_prefix = ".L" # use ".L" to hide labels from gdb
40 alt_label_prefix = ".L_ALT" # use ".L" to hide labels from gdb
41 style = None # interpreter style
42 generate_alt_table = False
45 class DataParseError(SyntaxError):
46 "Failure when parsing data file"
49 # Set any omnipresent substitution values.
51 def getGlobalSubDict():
52 return { "handler_size_bits":handler_size_bits,
53 "handler_size_bytes":handler_size_bytes }
56 # Parse arch config file --
57 # Set interpreter style.
59 def setHandlerStyle(tokens):
62 raise DataParseError("handler-style requires one argument")
64 if style != "computed-goto" and style != "jump-table" and style != "all-c":
65 raise DataParseError("handler-style (%s) invalid" % style)
68 # Parse arch config file --
69 # Set handler_size_bytes to the value of tokens[1], and handler_size_bits to
70 # log2(handler_size_bytes). Throws an exception if "bytes" is not 0 or
73 def setHandlerSize(tokens):
74 global handler_size_bits, handler_size_bytes
75 if style != "computed-goto":
76 print "Warning: handler-size valid only for computed-goto interpreters"
78 raise DataParseError("handler-size requires one argument")
79 if handler_size_bits != -1000:
80 raise DataParseError("handler-size may only be set once")
82 # compute log2(n), and make sure n is 0 or a power of 2
83 handler_size_bytes = bytes = int(tokens[1])
86 bytes //= 2 # halve with truncating division
89 if handler_size_bytes == 0 or handler_size_bytes != (1 << bits):
90 raise DataParseError("handler-size (%d) must be power of 2" \
92 handler_size_bits = bits
95 # Parse arch config file --
96 # Copy a file in to the C or asm output file.
98 def importFile(tokens):
100 raise DataParseError("import requires one argument")
102 if source.endswith(".cpp"):
103 appendSourceFile(tokens[1], getGlobalSubDict(), c_fp, None)
104 elif source.endswith(".S"):
105 appendSourceFile(tokens[1], getGlobalSubDict(), asm_fp, None)
107 raise DataParseError("don't know how to import %s (expecting .cpp/.S)"
111 # Parse arch config file --
112 # Copy a file in to the C or asm output file.
114 def setAsmStub(tokens):
117 print "Warning: asm-stub ignored for all-c interpreter"
119 raise DataParseError("import requires one argument")
121 stub_fp = open(tokens[1])
122 asm_stub_text = stub_fp.readlines()
125 raise DataParseError("unable to load asm-stub: %s" % str(err))
129 # Parse arch config file --
130 # Record location of default alt stub
132 def setAsmAltStub(tokens):
133 global default_alt_stub, generate_alt_table
135 print "Warning: asm-alt-stub ingored for all-c interpreter"
137 raise DataParseError("import requires one argument")
138 default_alt_stub = tokens[1]
139 generate_alt_table = True
142 # Parse arch config file --
143 # Start of opcode list.
147 global default_op_dir
149 raise DataParseError("opStart takes a directory name argument")
151 raise DataParseError("opStart can only be specified once")
152 default_op_dir = tokens[1]
156 # Parse arch config file --
157 # Set location of a single alt opcode's source file.
159 def altEntry(tokens):
160 global generate_alt_table
162 raise DataParseError("alt requires exactly two arguments")
164 raise DataParseError("alt statements must be between opStart/opEnd")
166 index = opcodes.index(tokens[1])
168 raise DataParseError("unknown opcode %s" % tokens[1])
169 if alt_opcode_locations.has_key(tokens[1]):
170 print "Note: alt overrides earlier %s (%s -> %s)" \
171 % (tokens[1], alt_opcode_locations[tokens[1]], tokens[2])
172 alt_opcode_locations[tokens[1]] = tokens[2]
173 generate_alt_table = True
176 # Parse arch config file --
177 # Set location of a single opcode's source file.
180 #global opcode_locations
182 raise DataParseError("op requires exactly two arguments")
184 raise DataParseError("op statements must be between opStart/opEnd")
186 index = opcodes.index(tokens[1])
188 raise DataParseError("unknown opcode %s" % tokens[1])
189 if opcode_locations.has_key(tokens[1]):
190 print "Note: op overrides earlier %s (%s -> %s)" \
191 % (tokens[1], opcode_locations[tokens[1]], tokens[2])
192 opcode_locations[tokens[1]] = tokens[2]
197 def emitJmpTable(start_label, prefix):
198 asm_fp.write("\n .global %s\n" % start_label)
199 asm_fp.write(" .text\n")
200 asm_fp.write("%s:\n" % start_label)
201 for i in xrange(kNumPackedOpcodes):
203 dict = getGlobalSubDict()
204 dict.update({ "opcode":op, "opnum":i })
205 asm_fp.write(" .long " + prefix + \
206 "_%(opcode)s /* 0x%(opnum)02x */\n" % dict)
209 # Parse arch config file --
210 # End of opcode list; emit instruction blocks.
215 raise DataParseError("opEnd takes no arguments")
217 raise DataParseError("opEnd must follow opStart, and only appear once")
221 if splitops == False:
222 if generate_alt_table:
223 loadAndEmitAltOpcodes()
224 if style == "jump-table":
225 emitJmpTable("dvmAsmInstructionStart", label_prefix);
226 emitJmpTable("dvmAsmAltInstructionStart", alt_label_prefix);
228 def genaltop(tokens):
230 raise DataParseError("alt-op can be specified only after op-end")
232 raise DataParseError("opEnd takes no arguments")
233 if generate_alt_table:
234 loadAndEmitAltOpcodes()
235 if style == "jump-table":
236 emitJmpTable("dvmAsmInstructionStart", label_prefix);
237 emitJmpTable("dvmAsmAltInstructionStart", alt_label_prefix);
241 # Extract an ordered list of instructions from the VM sources. We use the
242 # "goto table" definition macro, which has exactly kNumPackedOpcodes
247 opcode_fp = open(interp_defs_file)
248 opcode_re = re.compile(r"^\s*H\(OP_(\w+)\),.*", re.DOTALL)
249 for line in opcode_fp:
250 match = opcode_re.match(line)
253 opcodes.append("OP_" + match.group(1))
256 if len(opcodes) != kNumPackedOpcodes:
257 print "ERROR: found %d opcodes in Interp.h (expected %d)" \
258 % (len(opcodes), kNumPackedOpcodes)
259 raise SyntaxError, "bad opcode count"
263 if style == "computed-goto":
264 asm_fp.write(" .balign %d\n" % handler_size_bytes)
267 # Load and emit opcodes for all kNumPackedOpcodes instructions.
269 def loadAndEmitOpcodes():
271 assert len(opcodes) == kNumPackedOpcodes
272 need_dummy_start = False
273 if style == "jump-table":
274 start_label = "dvmAsmInstructionStartCode"
275 end_label = "dvmAsmInstructionEndCode"
277 start_label = "dvmAsmInstructionStart"
278 end_label = "dvmAsmInstructionEnd"
280 # point dvmAsmInstructionStart at the first handler or stub
281 asm_fp.write("\n .global %s\n" % start_label)
282 asm_fp.write(" .type %s, %%function\n" % start_label)
283 asm_fp.write("%s = " % start_label + label_prefix + "_OP_NOP\n")
284 asm_fp.write(" .text\n\n")
286 for i in xrange(kNumPackedOpcodes):
289 if opcode_locations.has_key(op):
290 location = opcode_locations[op]
292 location = default_op_dir
295 loadAndEmitC(location, i)
296 if len(asm_stub_text) == 0:
297 need_dummy_start = True
299 loadAndEmitAsm(location, i, sister_list)
301 # For a 100% C implementation, there are no asm handlers or stubs. We
302 # need to have the dvmAsmInstructionStart label point at OP_NOP, and it's
303 # too annoying to try to slide it in after the alignment psuedo-op, so
304 # we take the low road and just emit a dummy OP_NOP here.
307 asm_fp.write(label_prefix + "_OP_NOP: /* dummy */\n");
310 asm_fp.write(" .size %s, .-%s\n" % (start_label, start_label))
311 asm_fp.write(" .global %s\n" % end_label)
312 asm_fp.write("%s:\n" % end_label)
314 if style == "computed-goto":
315 emitSectionComment("Sister implementations", asm_fp)
316 asm_fp.write(" .global dvmAsmSisterStart\n")
317 asm_fp.write(" .type dvmAsmSisterStart, %function\n")
318 asm_fp.write(" .text\n")
319 asm_fp.write(" .balign 4\n")
320 asm_fp.write("dvmAsmSisterStart:\n")
321 asm_fp.writelines(sister_list)
322 asm_fp.write("\n .size dvmAsmSisterStart, .-dvmAsmSisterStart\n")
323 asm_fp.write(" .global dvmAsmSisterEnd\n")
324 asm_fp.write("dvmAsmSisterEnd:\n\n")
327 # Load an alternate entry stub
329 def loadAndEmitAltStub(source, opindex):
330 op = opcodes[opindex]
332 print " alt emit %s --> stub" % source
333 dict = getGlobalSubDict()
334 dict.update({ "opcode":op, "opnum":opindex })
336 emitAsmHeader(asm_fp, dict, alt_label_prefix)
337 appendSourceFile(source, dict, asm_fp, None)
340 # Load and emit alternate opcodes for all kNumPackedOpcodes instructions.
342 def loadAndEmitAltOpcodes():
343 assert len(opcodes) == kNumPackedOpcodes
344 if style == "jump-table":
345 start_label = "dvmAsmAltInstructionStartCode"
346 end_label = "dvmAsmAltInstructionEndCode"
348 start_label = "dvmAsmAltInstructionStart"
349 end_label = "dvmAsmAltInstructionEnd"
351 # point dvmAsmInstructionStart at the first handler or stub
352 asm_fp.write("\n .global %s\n" % start_label)
353 asm_fp.write(" .type %s, %%function\n" % start_label)
354 asm_fp.write(" .text\n\n")
355 asm_fp.write("%s = " % start_label + label_prefix + "_ALT_OP_NOP\n")
357 for i in xrange(kNumPackedOpcodes):
359 if alt_opcode_locations.has_key(op):
360 source = "%s/ALT_%s.S" % (alt_opcode_locations[op], op)
362 source = default_alt_stub
363 loadAndEmitAltStub(source, i)
366 asm_fp.write(" .size %s, .-%s\n" % (start_label, start_label))
367 asm_fp.write(" .global %s\n" % end_label)
368 asm_fp.write("%s:\n" % end_label)
371 # Load a C fragment and emit it, then output an assembly stub.
373 def loadAndEmitC(location, opindex):
374 op = opcodes[opindex]
375 source = "%s/%s.cpp" % (location, op)
377 print " emit %s --> C++" % source
378 dict = getGlobalSubDict()
379 dict.update({ "opcode":op, "opnum":opindex })
381 appendSourceFile(source, dict, c_fp, None)
383 if len(asm_stub_text) != 0:
384 emitAsmStub(asm_fp, dict)
387 # Load an assembly fragment and emit it.
389 def loadAndEmitAsm(location, opindex, sister_list):
390 op = opcodes[opindex]
391 source = "%s/%s.S" % (location, op)
392 dict = getGlobalSubDict()
393 dict.update({ "opcode":op, "opnum":opindex })
395 print " emit %s --> asm" % source
397 emitAsmHeader(asm_fp, dict, label_prefix)
398 appendSourceFile(source, dict, asm_fp, sister_list)
401 # Output the alignment directive and label for an assembly piece.
403 def emitAsmHeader(outfp, dict, prefix):
404 outfp.write("/* ------------------------------ */\n")
405 # The alignment directive ensures that the handler occupies
406 # at least the correct amount of space. We don't try to deal
407 # with overflow here.
409 # Emit a label so that gdb will say the right thing. We prepend an
410 # underscore so the symbol name doesn't clash with the Opcode enum.
411 outfp.write(prefix + "_%(opcode)s: /* 0x%(opnum)02x */\n" % dict)
414 # Output a generic instruction stub that updates the "glue" struct and
415 # calls the C implementation.
417 def emitAsmStub(outfp, dict):
418 emitAsmHeader(outfp, dict, label_prefix)
419 for line in asm_stub_text:
420 templ = Template(line)
421 outfp.write(templ.substitute(dict))
424 # Append the file specified by "source" to the open "outfp". Each line will
425 # be template-replaced using the substitution dictionary "dict".
427 # If the first line of the file starts with "%" it is taken as a directive.
428 # A "%include" line contains a filename and, optionally, a Python-style
429 # dictionary declaration with substitution strings. (This is implemented
432 # If "sister_list" is provided, and we find a line that contains only "&",
433 # all subsequent lines from the file will be appended to sister_list instead
434 # of copied to the output.
436 # This may modify "dict".
438 def appendSourceFile(source, dict, outfp, sister_list):
439 outfp.write("/* File: %s */\n" % source)
440 infp = open(source, "r")
443 if line.startswith("%include"):
444 # Parse the "include" line
445 tokens = line.strip().split(' ', 2)
447 raise DataParseError("malformed %%include in %s" % source)
449 alt_source = tokens[1].strip("\"")
450 if alt_source == source:
451 raise DataParseError("self-referential %%include in %s"
454 new_dict = dict.copy()
456 new_dict.update(eval(tokens[2]))
457 #print " including src=%s dict=%s" % (alt_source, new_dict)
458 appendSourceFile(alt_source, new_dict, outfp, sister_list)
461 elif line.startswith("%default"):
462 # copy keywords into dictionary
463 tokens = line.strip().split(' ', 1)
465 raise DataParseError("malformed %%default in %s" % source)
466 defaultValues = eval(tokens[1])
467 for entry in defaultValues:
468 dict.setdefault(entry, defaultValues[entry])
471 elif line.startswith("%verify"):
472 # more to come, someday
475 elif line.startswith("%break") and sister_list != None:
476 # allow more than one %break, ignoring all following the first
477 if style == "computed-goto" and not in_sister:
479 sister_list.append("\n/* continuation for %(opcode)s */\n"%dict)
482 # perform keyword substitution if a dictionary was provided
484 templ = Template(line)
486 subline = templ.substitute(dict)
487 except KeyError, err:
488 raise DataParseError("keyword substitution failed in %s: %s"
489 % (source, str(err)))
491 print "ERROR: substitution failed: " + line
496 # write output to appropriate file
498 sister_list.append(subline)
505 # Emit a C-style section header comment.
507 def emitSectionComment(str, fp):
508 equals = "========================================" \
509 "==================================="
511 fp.write("\n/*\n * %s\n * %s\n * %s\n */\n" %
512 (equals, str, equals))
516 # ===========================================================================
523 if len(sys.argv) != 3:
524 print "Usage: %s target-arch output-dir" % sys.argv[0]
527 target_arch = sys.argv[1]
528 output_dir = sys.argv[2]
531 # Extract opcode list.
533 opcodes = getOpcodeList()
541 config_fp = open("config-%s" % target_arch)
543 print "Unable to open config file 'config-%s'" % target_arch
547 # Open and prepare output files.
550 c_fp = open("%s/InterpC-%s.cpp" % (output_dir, target_arch), "w")
551 asm_fp = open("%s/InterpAsm-%s.S" % (output_dir, target_arch), "w")
553 print "Unable to open output files"
554 print "Make sure directory '%s' exists and existing files are writable" \
556 # Ideally we'd remove the files to avoid confusing "make", but if they
557 # failed to open we probably won't be able to remove them either.
560 print "Generating %s, %s" % (c_fp.name, asm_fp.name)
563 * This file was generated automatically by gen-mterp.py for '%s'.
565 * --> DO NOT EDIT <--
570 c_fp.write(file_header)
571 asm_fp.write(file_header)
574 # Process the config file.
578 for line in config_fp:
579 line = line.strip() # remove CRLF, leading spaces
580 tokens = line.split(' ') # tokenize
581 #print "%d: %s" % (len(tokens), tokens)
582 if len(tokens[0]) == 0:
585 elif tokens[0][0] == '#':
589 if tokens[0] == "handler-size":
590 setHandlerSize(tokens)
591 elif tokens[0] == "import":
593 elif tokens[0] == "asm-stub":
595 elif tokens[0] == "asm-alt-stub":
596 setAsmAltStub(tokens)
597 elif tokens[0] == "op-start":
599 elif tokens[0] == "op-end":
601 elif tokens[0] == "alt":
603 elif tokens[0] == "op":
605 elif tokens[0] == "handler-style":
606 setHandlerStyle(tokens)
607 elif tokens[0] == "alt-ops":
609 elif tokens[0] == "split-ops":
612 raise DataParseError, "unrecognized command '%s'" % tokens[0]
614 print "tokens[0] = %s" % tokens[0]
615 raise DataParseError, "handler-style must be first command"
616 except DataParseError, err:
617 print "Failed: " + str(err)
618 # TODO: remove output files so "make" doesn't get confused