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