1#!/usr/bin/env python
2#
3# Copyright (C) 2016 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 = "../../dex_instruction_list.h" # need opcode list
26kNumPackedOpcodes = 256
27
28splitops = False
29verbose = False
30handler_size_bits = -1000
31handler_size_bytes = -1000
32in_op_start = 0             # 0=not started, 1=started, 2=ended
33in_alt_op_start = 0         # 0=not started, 1=started, 2=ended
34default_op_dir = None
35default_alt_stub = None
36opcode_locations = {}
37alt_opcode_locations = {}
38asm_stub_text = []
39fallback_stub_text = []
40label_prefix = ".L"         # use ".L" to hide labels from gdb
41alt_label_prefix = ".L_ALT" # use ".L" to hide labels from gdb
42style = None                # interpreter style
43generate_alt_table = False
44function_type_format = ".type   %s, %%function"
45function_size_format = ".size   %s, .-%s"
46global_name_format = "%s"
47
48# Exception class.
49class DataParseError(SyntaxError):
50    "Failure when parsing data file"
51
52#
53# Set any omnipresent substitution values.
54#
55def getGlobalSubDict():
56    return { "handler_size_bits":handler_size_bits,
57             "handler_size_bytes":handler_size_bytes }
58
59#
60# Parse arch config file --
61# Set interpreter style.
62#
63def setHandlerStyle(tokens):
64    global style
65    if len(tokens) != 2:
66        raise DataParseError("handler-style requires one argument")
67    style = tokens[1]
68    if style != "computed-goto":
69        raise DataParseError("handler-style (%s) invalid" % style)
70
71#
72# Parse arch config file --
73# Set handler_size_bytes to the value of tokens[1], and handler_size_bits to
74# log2(handler_size_bytes).  Throws an exception if "bytes" is not 0 or
75# a power of two.
76#
77def setHandlerSize(tokens):
78    global handler_size_bits, handler_size_bytes
79    if style != "computed-goto":
80        print "Warning: handler-size valid only for computed-goto interpreters"
81    if len(tokens) != 2:
82        raise DataParseError("handler-size requires one argument")
83    if handler_size_bits != -1000:
84        raise DataParseError("handler-size may only be set once")
85
86    # compute log2(n), and make sure n is 0 or a power of 2
87    handler_size_bytes = bytes = int(tokens[1])
88    bits = -1
89    while bytes > 0:
90        bytes //= 2     # halve with truncating division
91        bits += 1
92
93    if handler_size_bytes == 0 or handler_size_bytes != (1 << bits):
94        raise DataParseError("handler-size (%d) must be power of 2" \
95                % orig_bytes)
96    handler_size_bits = bits
97
98#
99# Parse arch config file --
100# Copy a file in to asm output file.
101#
102def importFile(tokens):
103    if len(tokens) != 2:
104        raise DataParseError("import requires one argument")
105    source = tokens[1]
106    if source.endswith(".S"):
107        appendSourceFile(tokens[1], getGlobalSubDict(), asm_fp, None)
108    else:
109        raise DataParseError("don't know how to import %s (expecting .cpp/.S)"
110                % source)
111
112#
113# Parse arch config file --
114# Copy a file in to the C or asm output file.
115#
116def setAsmStub(tokens):
117    global asm_stub_text
118    if len(tokens) != 2:
119        raise DataParseError("import requires one argument")
120    try:
121        stub_fp = open(tokens[1])
122        asm_stub_text = stub_fp.readlines()
123    except IOError, err:
124        stub_fp.close()
125        raise DataParseError("unable to load asm-stub: %s" % str(err))
126    stub_fp.close()
127
128#
129# Parse arch config file --
130# Copy a file in to the C or asm output file.
131#
132def setFallbackStub(tokens):
133    global fallback_stub_text
134    if len(tokens) != 2:
135        raise DataParseError("import requires one argument")
136    try:
137        stub_fp = open(tokens[1])
138        fallback_stub_text = stub_fp.readlines()
139    except IOError, err:
140        stub_fp.close()
141        raise DataParseError("unable to load fallback-stub: %s" % str(err))
142    stub_fp.close()
143#
144# Parse arch config file --
145# Record location of default alt stub
146#
147def setAsmAltStub(tokens):
148    global default_alt_stub, generate_alt_table
149    if len(tokens) != 2:
150        raise DataParseError("import requires one argument")
151    default_alt_stub = tokens[1]
152    generate_alt_table = True
153#
154# Change the default function type format
155#
156def setFunctionTypeFormat(tokens):
157    global function_type_format
158    function_type_format = tokens[1]
159#
160# Change the default function size format
161#
162def setFunctionSizeFormat(tokens):
163    global function_size_format
164    function_size_format = tokens[1]
165#
166# Change the global name format
167#
168def setGlobalNameFormat(tokens):
169    global global_name_format
170    global_name_format = tokens[1]
171#
172# Parse arch config file --
173# Start of opcode list.
174#
175def opStart(tokens):
176    global in_op_start
177    global default_op_dir
178    if len(tokens) != 2:
179        raise DataParseError("opStart takes a directory name argument")
180    if in_op_start != 0:
181        raise DataParseError("opStart can only be specified once")
182    default_op_dir = tokens[1]
183    in_op_start = 1
184
185#
186# Parse arch config file --
187# Set location of a single alt opcode's source file.
188#
189def altEntry(tokens):
190    global generate_alt_table
191    if len(tokens) != 3:
192        raise DataParseError("alt requires exactly two arguments")
193    if in_op_start != 1:
194        raise DataParseError("alt statements must be between opStart/opEnd")
195    try:
196        index = opcodes.index(tokens[1])
197    except ValueError:
198        raise DataParseError("unknown opcode %s" % tokens[1])
199    if alt_opcode_locations.has_key(tokens[1]):
200        print "Note: alt overrides earlier %s (%s -> %s)" \
201                % (tokens[1], alt_opcode_locations[tokens[1]], tokens[2])
202    alt_opcode_locations[tokens[1]] = tokens[2]
203    generate_alt_table = True
204
205#
206# Parse arch config file --
207# Set location of a single opcode's source file.
208#
209def opEntry(tokens):
210    #global opcode_locations
211    if len(tokens) != 3:
212        raise DataParseError("op requires exactly two arguments")
213    if in_op_start != 1:
214        raise DataParseError("op statements must be between opStart/opEnd")
215    try:
216        index = opcodes.index(tokens[1])
217    except ValueError:
218        raise DataParseError("unknown opcode %s" % tokens[1])
219    if opcode_locations.has_key(tokens[1]):
220        print "Note: op overrides earlier %s (%s -> %s)" \
221                % (tokens[1], opcode_locations[tokens[1]], tokens[2])
222    opcode_locations[tokens[1]] = tokens[2]
223
224#
225# Parse arch config file --
226# End of opcode list; emit instruction blocks.
227#
228def opEnd(tokens):
229    global in_op_start
230    if len(tokens) != 1:
231        raise DataParseError("opEnd takes no arguments")
232    if in_op_start != 1:
233        raise DataParseError("opEnd must follow opStart, and only appear once")
234    in_op_start = 2
235
236    loadAndEmitOpcodes()
237    if splitops == False:
238        if generate_alt_table:
239            loadAndEmitAltOpcodes()
240
241def genaltop(tokens):
242    if in_op_start != 2:
243       raise DataParseError("alt-op can be specified only after op-end")
244    if len(tokens) != 1:
245        raise DataParseError("opEnd takes no arguments")
246    if generate_alt_table:
247        loadAndEmitAltOpcodes()
248
249#
250# Extract an ordered list of instructions from the VM sources.  We use the
251# "goto table" definition macro, which has exactly kNumPackedOpcodes
252# entries.
253#
254def getOpcodeList():
255    opcodes = []
256    opcode_fp = open(interp_defs_file)
257    opcode_re = re.compile(r"^\s*V\((....), (\w+),.*", re.DOTALL)
258    for line in opcode_fp:
259        match = opcode_re.match(line)
260        if not match:
261            continue
262        opcodes.append("op_" + match.group(2).lower())
263    opcode_fp.close()
264
265    if len(opcodes) != kNumPackedOpcodes:
266        print "ERROR: found %d opcodes in Interp.h (expected %d)" \
267                % (len(opcodes), kNumPackedOpcodes)
268        raise SyntaxError, "bad opcode count"
269    return opcodes
270
271def emitAlign():
272    if style == "computed-goto":
273        asm_fp.write("    .balign %d\n" % handler_size_bytes)
274
275#
276# Load and emit opcodes for all kNumPackedOpcodes instructions.
277#
278def loadAndEmitOpcodes():
279    sister_list = []
280    assert len(opcodes) == kNumPackedOpcodes
281    need_dummy_start = False
282    start_label = global_name_format % "artMterpAsmInstructionStart"
283    end_label = global_name_format % "artMterpAsmInstructionEnd"
284
285    # point MterpAsmInstructionStart at the first handler or stub
286    asm_fp.write("\n    .global %s\n" % start_label)
287    asm_fp.write("    " + (function_type_format % start_label) + "\n");
288    asm_fp.write("%s = " % start_label + label_prefix + "_op_nop\n")
289    asm_fp.write("    .text\n\n")
290
291    for i in xrange(kNumPackedOpcodes):
292        op = opcodes[i]
293
294        if opcode_locations.has_key(op):
295            location = opcode_locations[op]
296        else:
297            location = default_op_dir
298
299        if location == "FALLBACK":
300            emitFallback(i)
301        else:
302            loadAndEmitAsm(location, i, sister_list)
303
304    # For a 100% C implementation, there are no asm handlers or stubs.  We
305    # need to have the MterpAsmInstructionStart label point at op_nop, and it's
306    # too annoying to try to slide it in after the alignment psuedo-op, so
307    # we take the low road and just emit a dummy op_nop here.
308    if need_dummy_start:
309        emitAlign()
310        asm_fp.write(label_prefix + "_op_nop:   /* dummy */\n");
311
312    emitAlign()
313    asm_fp.write("    " + (function_size_format % (start_label, start_label)) + "\n")
314    asm_fp.write("    .global %s\n" % end_label)
315    asm_fp.write("%s:\n" % end_label)
316
317    if style == "computed-goto":
318        start_sister_label = global_name_format % "artMterpAsmSisterStart"
319        end_sister_label = global_name_format % "artMterpAsmSisterEnd"
320        emitSectionComment("Sister implementations", asm_fp)
321        asm_fp.write("    .global %s\n" % start_sister_label)
322        asm_fp.write("    " + (function_type_format % start_sister_label) + "\n");
323        asm_fp.write("    .text\n")
324        asm_fp.write("    .balign 4\n")
325        asm_fp.write("%s:\n" % start_sister_label)
326        asm_fp.writelines(sister_list)
327        asm_fp.write("\n    " + (function_size_format % (start_sister_label, start_sister_label)) + "\n")
328        asm_fp.write("    .global %s\n" % end_sister_label)
329        asm_fp.write("%s:\n\n" % end_sister_label)
330
331#
332# Load an alternate entry stub
333#
334def loadAndEmitAltStub(source, opindex):
335    op = opcodes[opindex]
336    if verbose:
337        print " alt emit %s --> stub" % source
338    dict = getGlobalSubDict()
339    dict.update({ "opcode":op, "opnum":opindex })
340
341    emitAsmHeader(asm_fp, dict, alt_label_prefix)
342    appendSourceFile(source, dict, asm_fp, None)
343
344#
345# Load and emit alternate opcodes for all kNumPackedOpcodes instructions.
346#
347def loadAndEmitAltOpcodes():
348    assert len(opcodes) == kNumPackedOpcodes
349    start_label = global_name_format % "artMterpAsmAltInstructionStart"
350    end_label = global_name_format % "artMterpAsmAltInstructionEnd"
351
352    # point MterpAsmInstructionStart at the first handler or stub
353    asm_fp.write("\n    .global %s\n" % start_label)
354    asm_fp.write("    " + (function_type_format % start_label) + "\n");
355    asm_fp.write("    .text\n\n")
356    asm_fp.write("%s = " % start_label + label_prefix + "_ALT_op_nop\n")
357
358    for i in xrange(kNumPackedOpcodes):
359        op = opcodes[i]
360        if alt_opcode_locations.has_key(op):
361            source = "%s/alt_%s.S" % (alt_opcode_locations[op], op)
362        else:
363            source = default_alt_stub
364        loadAndEmitAltStub(source, i)
365
366    emitAlign()
367    asm_fp.write("    " + (function_size_format % (start_label, start_label)) + "\n")
368    asm_fp.write("    .global %s\n" % end_label)
369    asm_fp.write("%s:\n" % end_label)
370
371#
372# Load an assembly fragment and emit it.
373#
374def loadAndEmitAsm(location, opindex, sister_list):
375    op = opcodes[opindex]
376    source = "%s/%s.S" % (location, op)
377    dict = getGlobalSubDict()
378    dict.update({ "opcode":op, "opnum":opindex })
379    if verbose:
380        print " emit %s --> asm" % source
381
382    emitAsmHeader(asm_fp, dict, label_prefix)
383    appendSourceFile(source, dict, asm_fp, sister_list)
384
385#
386# Emit fallback fragment
387#
388def emitFallback(opindex):
389    op = opcodes[opindex]
390    dict = getGlobalSubDict()
391    dict.update({ "opcode":op, "opnum":opindex })
392    emitAsmHeader(asm_fp, dict, label_prefix)
393    for line in fallback_stub_text:
394        asm_fp.write(line)
395    asm_fp.write("\n")
396
397#
398# Output the alignment directive and label for an assembly piece.
399#
400def emitAsmHeader(outfp, dict, prefix):
401    outfp.write("/* ------------------------------ */\n")
402    # The alignment directive ensures that the handler occupies
403    # at least the correct amount of space.  We don't try to deal
404    # with overflow here.
405    emitAlign()
406    # Emit a label so that gdb will say the right thing.  We prepend an
407    # underscore so the symbol name doesn't clash with the Opcode enum.
408    outfp.write(prefix + "_%(opcode)s: /* 0x%(opnum)02x */\n" % dict)
409
410#
411# Output a generic instruction stub that updates the "glue" struct and
412# calls the C implementation.
413#
414def emitAsmStub(outfp, dict):
415    emitAsmHeader(outfp, dict, label_prefix)
416    for line in asm_stub_text:
417        templ = Template(line)
418        outfp.write(templ.substitute(dict))
419
420#
421# Append the file specified by "source" to the open "outfp".  Each line will
422# be template-replaced using the substitution dictionary "dict".
423#
424# If the first line of the file starts with "%" it is taken as a directive.
425# A "%include" line contains a filename and, optionally, a Python-style
426# dictionary declaration with substitution strings.  (This is implemented
427# with recursion.)
428#
429# If "sister_list" is provided, and we find a line that contains only "&",
430# all subsequent lines from the file will be appended to sister_list instead
431# of copied to the output.
432#
433# This may modify "dict".
434#
435def appendSourceFile(source, dict, outfp, sister_list):
436    outfp.write("/* File: %s */\n" % source)
437    infp = open(source, "r")
438    in_sister = False
439    for line in infp:
440        if line.startswith("%include"):
441            # Parse the "include" line
442            tokens = line.strip().split(' ', 2)
443            if len(tokens) < 2:
444                raise DataParseError("malformed %%include in %s" % source)
445
446            alt_source = tokens[1].strip("\"")
447            if alt_source == source:
448                raise DataParseError("self-referential %%include in %s"
449                        % source)
450
451            new_dict = dict.copy()
452            if len(tokens) == 3:
453                new_dict.update(eval(tokens[2]))
454            #print " including src=%s dict=%s" % (alt_source, new_dict)
455            appendSourceFile(alt_source, new_dict, outfp, sister_list)
456            continue
457
458        elif line.startswith("%default"):
459            # copy keywords into dictionary
460            tokens = line.strip().split(' ', 1)
461            if len(tokens) < 2:
462                raise DataParseError("malformed %%default in %s" % source)
463            defaultValues = eval(tokens[1])
464            for entry in defaultValues:
465                dict.setdefault(entry, defaultValues[entry])
466            continue
467
468        elif line.startswith("%break") and sister_list != None:
469            # allow more than one %break, ignoring all following the first
470            if style == "computed-goto" and not in_sister:
471                in_sister = True
472                sister_list.append("\n/* continuation for %(opcode)s */\n"%dict)
473            continue
474
475        # perform keyword substitution if a dictionary was provided
476        if dict != None:
477            templ = Template(line)
478            try:
479                subline = templ.substitute(dict)
480            except KeyError, err:
481                raise DataParseError("keyword substitution failed in %s: %s"
482                        % (source, str(err)))
483            except:
484                print "ERROR: substitution failed: " + line
485                raise
486        else:
487            subline = line
488
489        # write output to appropriate file
490        if in_sister:
491            sister_list.append(subline)
492        else:
493            outfp.write(subline)
494    outfp.write("\n")
495    infp.close()
496
497#
498# Emit a C-style section header comment.
499#
500def emitSectionComment(str, fp):
501    equals = "========================================" \
502             "==================================="
503
504    fp.write("\n/*\n * %s\n *  %s\n * %s\n */\n" %
505        (equals, str, equals))
506
507
508#
509# ===========================================================================
510# "main" code
511#
512
513#
514# Check args.
515#
516if len(sys.argv) != 3:
517    print "Usage: %s target-arch output-dir" % sys.argv[0]
518    sys.exit(2)
519
520target_arch = sys.argv[1]
521output_dir = sys.argv[2]
522
523#
524# Extract opcode list.
525#
526opcodes = getOpcodeList()
527#for op in opcodes:
528#    print "  %s" % op
529
530#
531# Open config file.
532#
533try:
534    config_fp = open("config_%s" % target_arch)
535except:
536    print "Unable to open config file 'config_%s'" % target_arch
537    sys.exit(1)
538
539#
540# Open and prepare output files.
541#
542try:
543    asm_fp = open("%s/mterp_%s.S" % (output_dir, target_arch), "w")
544except:
545    print "Unable to open output files"
546    print "Make sure directory '%s' exists and existing files are writable" \
547            % output_dir
548    # Ideally we'd remove the files to avoid confusing "make", but if they
549    # failed to open we probably won't be able to remove them either.
550    sys.exit(1)
551
552print "Generating %s" % (asm_fp.name)
553
554file_header = """/*
555 * This file was generated automatically by gen-mterp.py for '%s'.
556 *
557 * --> DO NOT EDIT <--
558 */
559
560""" % (target_arch)
561
562asm_fp.write(file_header)
563
564#
565# Process the config file.
566#
567failed = False
568try:
569    for line in config_fp:
570        line = line.strip()         # remove CRLF, leading spaces
571        tokens = line.split(' ')    # tokenize
572        #print "%d: %s" % (len(tokens), tokens)
573        if len(tokens[0]) == 0:
574            #print "  blank"
575            pass
576        elif tokens[0][0] == '#':
577            #print "  comment"
578            pass
579        else:
580            if tokens[0] == "handler-size":
581                setHandlerSize(tokens)
582            elif tokens[0] == "import":
583                importFile(tokens)
584            elif tokens[0] == "asm-stub":
585                setAsmStub(tokens)
586            elif tokens[0] == "asm-alt-stub":
587                setAsmAltStub(tokens)
588            elif tokens[0] == "op-start":
589                opStart(tokens)
590            elif tokens[0] == "op-end":
591                opEnd(tokens)
592            elif tokens[0] == "alt":
593                altEntry(tokens)
594            elif tokens[0] == "op":
595                opEntry(tokens)
596            elif tokens[0] == "handler-style":
597                setHandlerStyle(tokens)
598            elif tokens[0] == "alt-ops":
599                genaltop(tokens)
600            elif tokens[0] == "split-ops":
601                splitops = True
602            elif tokens[0] == "fallback-stub":
603               setFallbackStub(tokens)
604            elif tokens[0] == "function-type-format":
605               setFunctionTypeFormat(tokens)
606            elif tokens[0] == "function-size-format":
607               setFunctionSizeFormat(tokens)
608            elif tokens[0] == "global-name-format":
609               setGlobalNameFormat(tokens)
610            else:
611                raise DataParseError, "unrecognized command '%s'" % tokens[0]
612            if style == None:
613                print "tokens[0] = %s" % tokens[0]
614                raise DataParseError, "handler-style must be first command"
615except DataParseError, err:
616    print "Failed: " + str(err)
617    # TODO: remove output files so "make" doesn't get confused
618    failed = True
619    asm_fp.close()
620    asm_fp = None
621
622config_fp.close()
623
624#
625# Done!
626#
627if asm_fp:
628    asm_fp.close()
629
630sys.exit(failed)
631