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