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