blob: 5839b5fc97c062197403a7840a45dc60abdd70cf [file] [log] [blame]
buzbee1452bee2015-03-06 14:43:04 -08001#!/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
Serguei Katkov05dfaaa2016-01-28 08:21:26 +060044function_type_format = ".type %s, %%function"
45function_size_format = ".size %s, .-%s"
46global_name_format = "%s"
buzbee1452bee2015-03-06 14:43:04 -080047
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
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600153#
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]
buzbee1452bee2015-03-06 14:43:04 -0800171#
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
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600282 start_label = global_name_format % "artMterpAsmInstructionStart"
283 end_label = global_name_format % "artMterpAsmInstructionEnd"
buzbee1452bee2015-03-06 14:43:04 -0800284
285 # point MterpAsmInstructionStart at the first handler or stub
286 asm_fp.write("\n .global %s\n" % start_label)
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600287 asm_fp.write(" " + (function_type_format % start_label) + "\n");
buzbee1452bee2015-03-06 14:43:04 -0800288 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()
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600313 asm_fp.write(" " + (function_size_format % (start_label, start_label)) + "\n")
buzbee1452bee2015-03-06 14:43:04 -0800314 asm_fp.write(" .global %s\n" % end_label)
315 asm_fp.write("%s:\n" % end_label)
316
317 if style == "computed-goto":
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600318 start_sister_label = global_name_format % "artMterpAsmSisterStart"
319 end_sister_label = global_name_format % "artMterpAsmSisterEnd"
buzbee1452bee2015-03-06 14:43:04 -0800320 emitSectionComment("Sister implementations", asm_fp)
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600321 asm_fp.write(" .global %s\n" % start_sister_label)
322 asm_fp.write(" " + (function_type_format % start_sister_label) + "\n");
buzbee1452bee2015-03-06 14:43:04 -0800323 asm_fp.write(" .text\n")
324 asm_fp.write(" .balign 4\n")
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600325 asm_fp.write("%s:\n" % start_sister_label)
buzbee1452bee2015-03-06 14:43:04 -0800326 asm_fp.writelines(sister_list)
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600327 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)
buzbee1452bee2015-03-06 14:43:04 -0800330
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
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600349 start_label = global_name_format % "artMterpAsmAltInstructionStart"
350 end_label = global_name_format % "artMterpAsmAltInstructionEnd"
buzbee1452bee2015-03-06 14:43:04 -0800351
352 # point MterpAsmInstructionStart at the first handler or stub
353 asm_fp.write("\n .global %s\n" % start_label)
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600354 asm_fp.write(" " + (function_type_format % start_label) + "\n");
buzbee1452bee2015-03-06 14:43:04 -0800355 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()
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600367 asm_fp.write(" " + (function_size_format % (start_label, start_label)) + "\n")
buzbee1452bee2015-03-06 14:43:04 -0800368 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)
Serguei Katkov05dfaaa2016-01-28 08:21:26 +0600604 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)
buzbee1452bee2015-03-06 14:43:04 -0800610 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)