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