OSDN Git Service

Merge "Return unsigned shorts in ShortArrayCodeInput." into dalvik-dev
[android-x86/dalvik.git] / vm / mterp / gen-mterp.py
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
22 import sys, string, re, time
23 from string import Template
24
25 interp_defs_file = "../../libdex/DexOpcodes.h" # need opcode list
26 kNumPackedOpcodes = 512 # TODO: Derive this from DexOpcodes.h.
27
28 verbose = False
29 handler_size_bits = -1000
30 handler_size_bytes = -1000
31 in_op_start = 0             # 0=not started, 1=started, 2=ended
32 default_op_dir = None
33 opcode_locations = {}
34 asm_stub_text = []
35 label_prefix = ".L"         # use ".L" to hide labels from gdb
36
37 # Exception class.
38 class DataParseError(SyntaxError):
39     "Failure when parsing data file"
40
41 #
42 # Set any omnipresent substitution values.
43 #
44 def getGlobalSubDict():
45     return { "handler_size_bits":handler_size_bits,
46              "handler_size_bytes":handler_size_bytes }
47
48 #
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
52 # of two.
53 #
54 def setHandlerSize(tokens):
55     global handler_size_bits, handler_size_bytes
56     if len(tokens) != 2:
57         raise DataParseError("handler-size requires one argument")
58     if handler_size_bits != -1000:
59         raise DataParseError("handler-size may only be set once")
60
61     # compute log2(n), and make sure n is a power of 2
62     handler_size_bytes = bytes = int(tokens[1])
63     bits = -1
64     while bytes > 0:
65         bytes //= 2     # halve with truncating division
66         bits += 1
67
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" \
70                 % orig_bytes)
71     handler_size_bits = bits
72
73 #
74 # Parse arch config file --
75 # Copy a file in to the C or asm output file.
76 #
77 def importFile(tokens):
78     if len(tokens) != 2:
79         raise DataParseError("import requires one argument")
80     source = tokens[1]
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)
85     else:
86         raise DataParseError("don't know how to import %s (expecting .c/.S)"
87                 % source)
88
89 #
90 # Parse arch config file --
91 # Copy a file in to the C or asm output file.
92 #
93 def setAsmStub(tokens):
94     global asm_stub_text
95     if len(tokens) != 2:
96         raise DataParseError("import requires one argument")
97     try:
98         stub_fp = open(tokens[1])
99         asm_stub_text = stub_fp.readlines()
100     except IOError, err:
101         stub_fp.close()
102         raise DataParseError("unable to load asm-stub: %s" % str(err))
103     stub_fp.close()
104
105 #
106 # Parse arch config file --
107 # Start of opcode list.
108 #
109 def opStart(tokens):
110     global in_op_start
111     global default_op_dir
112     if len(tokens) != 2:
113         raise DataParseError("opStart takes a directory name argument")
114     if in_op_start != 0:
115         raise DataParseError("opStart can only be specified once")
116     default_op_dir = tokens[1]
117     in_op_start = 1
118
119 #
120 # Parse arch config file --
121 # Set location of a single opcode's source file.
122 #
123 def opEntry(tokens):
124     #global opcode_locations
125     if len(tokens) != 3:
126         raise DataParseError("op requires exactly two arguments")
127     if in_op_start != 1:
128         raise DataParseError("op statements must be between opStart/opEnd")
129     try:
130         index = opcodes.index(tokens[1])
131     except ValueError:
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]
137
138 #
139 # Parse arch config file --
140 # End of opcode list; emit instruction blocks.
141 #
142 def opEnd(tokens):
143     global in_op_start
144     if len(tokens) != 1:
145         raise DataParseError("opEnd takes no arguments")
146     if in_op_start != 1:
147         raise DataParseError("opEnd must follow opStart, and only appear once")
148     in_op_start = 2
149
150     loadAndEmitOpcodes()
151
152
153 #
154 # Extract an ordered list of instructions from the VM sources.  We use the
155 # "goto table" definition macro, which has exactly kNumPackedOpcodes
156 # entries.
157 #
158 def getOpcodeList():
159     opcodes = []
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)
164         if not match:
165             continue
166         opcodes.append("OP_" + match.group(1))
167     opcode_fp.close()
168
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"
173     return opcodes
174
175
176 #
177 # Load and emit opcodes for all kNumPackedOpcodes instructions.
178 #
179 def loadAndEmitOpcodes():
180     sister_list = []
181     assert len(opcodes) == kNumPackedOpcodes
182     need_dummy_start = False
183
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")
189
190     for i in xrange(kNumPackedOpcodes):
191         op = opcodes[i]
192
193         if opcode_locations.has_key(op):
194             location = opcode_locations[op]
195         else:
196             location = default_op_dir
197
198         if location == "c":
199             loadAndEmitC(location, i)
200             if len(asm_stub_text) == 0:
201                 need_dummy_start = True
202         else:
203             loadAndEmitAsm(location, i, sister_list)
204
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.
209     #
210     # The dvmAsmInstructionStart will also use the size for OP_NOP, so
211     # we want to generate a .size directive that spans all handlers.
212     if need_dummy_start:
213         asm_fp.write("    .balign %d\n" % handler_size_bytes)
214         asm_fp.write(label_prefix + "_OP_NOP:   /* dummy */\n");
215
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")
220
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)
228
229     asm_fp.write("\n    .size   dvmAsmSisterStart, .-dvmAsmSisterStart\n")
230     asm_fp.write("    .global dvmAsmSisterEnd\n")
231     asm_fp.write("dvmAsmSisterEnd:\n\n")
232
233 #
234 # Load a C fragment and emit it, then output an assembly stub.
235 #
236 def loadAndEmitC(location, opindex):
237     op = opcodes[opindex]
238     source = "%s/%s.c" % (location, op)
239     if verbose:
240         print " emit %s --> C" % source
241     dict = getGlobalSubDict()
242     dict.update({ "opcode":op, "opnum":opindex })
243
244     appendSourceFile(source, dict, c_fp, None)
245
246     if len(asm_stub_text) != 0:
247         emitAsmStub(asm_fp, dict)
248
249 #
250 # Load an assembly fragment and emit it.
251 #
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 })
257     if verbose:
258         print " emit %s --> asm" % source
259
260     emitAsmHeader(asm_fp, dict)
261     appendSourceFile(source, dict, asm_fp, sister_list)
262
263 #
264 # Output the alignment directive and label for an assembly piece.
265 #
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)
275
276 #
277 # Output a generic instruction stub that updates the "glue" struct and
278 # calls the C implementation.
279 #
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))
285
286 #
287 # Append the file specified by "source" to the open "outfp".  Each line will
288 # be template-replaced using the substitution dictionary "dict".
289 #
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
293 # with recursion.)
294 #
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.
298 #
299 # This may modify "dict".
300 #
301 def appendSourceFile(source, dict, outfp, sister_list):
302     outfp.write("/* File: %s */\n" % source)
303     infp = open(source, "r")
304     in_sister = False
305     for line in infp:
306         if line.startswith("%include"):
307             # Parse the "include" line
308             tokens = line.strip().split(' ', 2)
309             if len(tokens) < 2:
310                 raise DataParseError("malformed %%include in %s" % source)
311
312             alt_source = tokens[1].strip("\"")
313             if alt_source == source:
314                 raise DataParseError("self-referential %%include in %s"
315                         % source)
316
317             new_dict = dict.copy()
318             if len(tokens) == 3:
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)
322             continue
323
324         elif line.startswith("%default"):
325             # copy keywords into dictionary
326             tokens = line.strip().split(' ', 1)
327             if len(tokens) < 2:
328                 raise DataParseError("malformed %%default in %s" % source)
329             defaultValues = eval(tokens[1])
330             for entry in defaultValues:
331                 dict.setdefault(entry, defaultValues[entry])
332             continue
333
334         elif line.startswith("%verify"):
335             # more to come, someday
336             continue
337
338         elif line.startswith("%break") and sister_list != None:
339             # allow more than one %break, ignoring all following the first
340             if not in_sister:
341                 in_sister = True
342                 sister_list.append("\n/* continuation for %(opcode)s */\n"%dict)
343             continue
344
345         # perform keyword substitution if a dictionary was provided
346         if dict != None:
347             templ = Template(line)
348             try:
349                 subline = templ.substitute(dict)
350             except KeyError, err:
351                 raise DataParseError("keyword substitution failed in %s: %s"
352                         % (source, str(err)))
353             except:
354                 print "ERROR: substitution failed: " + line
355                 raise
356         else:
357             subline = line
358
359         # write output to appropriate file
360         if in_sister:
361             sister_list.append(subline)
362         else:
363             outfp.write(subline)
364     outfp.write("\n")
365     infp.close()
366
367 #
368 # Emit a C-style section header comment.
369 #
370 def emitSectionComment(str, fp):
371     equals = "========================================" \
372              "==================================="
373
374     fp.write("\n/*\n * %s\n *  %s\n * %s\n */\n" %
375         (equals, str, equals))
376
377
378 #
379 # ===========================================================================
380 # "main" code
381 #
382
383 #
384 # Check args.
385 #
386 if len(sys.argv) != 3:
387     print "Usage: %s target-arch output-dir" % sys.argv[0]
388     sys.exit(2)
389
390 target_arch = sys.argv[1]
391 output_dir = sys.argv[2]
392
393 #
394 # Extract opcode list.
395 #
396 opcodes = getOpcodeList()
397 #for op in opcodes:
398 #    print "  %s" % op
399
400 #
401 # Open config file.
402 #
403 try:
404     config_fp = open("config-%s" % target_arch)
405 except:
406     print "Unable to open config file 'config-%s'" % target_arch
407     sys.exit(1)
408
409 #
410 # Open and prepare output files.
411 #
412 try:
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")
415 except:
416     print "Unable to open output files"
417     print "Make sure directory '%s' exists and existing files are writable" \
418             % output_dir
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.
421     sys.exit(1)
422
423 print "Generating %s, %s" % (c_fp.name, asm_fp.name)
424
425 file_header = """/*
426  * This file was generated automatically by gen-mterp.py for '%s'.
427  *
428  * --> DO NOT EDIT <--
429  */
430
431 """ % (target_arch)
432
433 c_fp.write(file_header)
434 asm_fp.write(file_header)
435
436 #
437 # Process the config file.
438 #
439 failed = False
440 try:
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:
446             #print "  blank"
447             pass
448         elif tokens[0][0] == '#':
449             #print "  comment"
450             pass
451         else:
452             if tokens[0] == "handler-size":
453                 setHandlerSize(tokens)
454             elif tokens[0] == "import":
455                 importFile(tokens)
456             elif tokens[0] == "asm-stub":
457                 setAsmStub(tokens)
458             elif tokens[0] == "op-start":
459                 opStart(tokens)
460             elif tokens[0] == "op-end":
461                 opEnd(tokens)
462             elif tokens[0] == "op":
463                 opEntry(tokens)
464             else:
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
469     failed = True
470     c_fp.close()
471     asm_fp.close()
472     c_fp = asm_fp = None
473
474 config_fp.close()
475
476 #
477 # Done!
478 #
479 if c_fp:
480     c_fp.close()
481 if asm_fp:
482     asm_fp.close()
483
484 sys.exit(failed)