arch: clean up isa_parser error handling

Although some decent error messages were getting generated inside
isa_parser.py, they weren't always getting printed because of the
screwy way we were handling exceptions.  (Basically an inner
exception would get hidden by an outer exception, and the more
informative inner error message would not get printed.)

Also line numbers were messed up, since they were taken from the
lexer, which is typically a token (or more) ahead of the grammar
rule that's being matched.  Using the 'lineno' attribute that
PLY associates with the grammar production is more accurate.
The new LineTracker class extends lineno to track filenames as
well as line numbers.
This commit is contained in:
Steve Reinhardt 2015-10-06 17:26:50 -07:00
parent 2511490c9c
commit 90c279e4b1

View file

@ -11,7 +11,7 @@
# modified or unmodified, in source code or in binary form. # modified or unmodified, in source code or in binary form.
# #
# Copyright (c) 2003-2005 The Regents of The University of Michigan # Copyright (c) 2003-2005 The Regents of The University of Michigan
# Copyright (c) 2013 Advanced Micro Devices, Inc. # Copyright (c) 2013,2015 Advanced Micro Devices, Inc.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -88,42 +88,17 @@ def fixPythonIndentation(s):
return s return s
class ISAParserError(Exception): class ISAParserError(Exception):
"""Error handler for parser errors""" """Exception class for parser errors"""
def __init__(self, first, second=None): def __init__(self, first, second=None):
if second is None: if second is None:
self.lineno = 0 self.lineno = 0
self.string = first self.string = first
else: else:
if hasattr(first, 'lexer'):
first = first.lexer.lineno
self.lineno = first self.lineno = first
self.string = second self.string = second
def display(self, filename_stack, print_traceback=debug): def __str__(self):
# Output formatted to work under Emacs compile-mode. Optional return self.string
# 'print_traceback' arg, if set to True, prints a Python stack
# backtrace too (can be handy when trying to debug the parser
# itself).
spaces = ""
for (filename, line) in filename_stack[:-1]:
print "%sIn file included from %s:" % (spaces, filename)
spaces += " "
# Print a Python stack backtrace if requested.
if print_traceback or not self.lineno:
traceback.print_exc()
line_str = "%s:" % (filename_stack[-1][0], )
if self.lineno:
line_str += "%d:" % (self.lineno, )
return "%s%s %s" % (spaces, line_str, self.string)
def exit(self, filename_stack, print_traceback=debug):
# Just call exit.
sys.exit(self.display(filename_stack, print_traceback))
def error(*args): def error(*args):
raise ISAParserError(*args) raise ISAParserError(*args)
@ -1022,8 +997,8 @@ class SubOperandList(OperandList):
# find this op in the master list # find this op in the master list
op_desc = master_list.find_base(op_base) op_desc = master_list.find_base(op_base)
if not op_desc: if not op_desc:
error('Found operand %s which is not in the master list!' \ error('Found operand %s which is not in the master list!'
' This is an internal error' % op_base) % op_base)
else: else:
# See if we've already found this operand # See if we've already found this operand
op_desc = self.find_base(op_base) op_desc = self.find_base(op_base)
@ -1187,6 +1162,39 @@ class Stack(list):
def top(self): def top(self):
return self[-1] return self[-1]
# Format a file include stack backtrace as a string
def backtrace(filename_stack):
fmt = "In file included from %s:"
return "\n".join([fmt % f for f in filename_stack])
#######################
#
# LineTracker: track filenames along with line numbers in PLY lineno fields
# PLY explicitly doesn't do anything with 'lineno' except propagate
# it. This class lets us tie filenames with the line numbers with a
# minimum of disruption to existing increment code.
#
class LineTracker(object):
def __init__(self, filename, lineno=1):
self.filename = filename
self.lineno = lineno
# Overload '+=' for increments. We need to create a new object on
# each update else every token ends up referencing the same
# constantly incrementing instance.
def __iadd__(self, incr):
return LineTracker(self.filename, self.lineno + incr)
def __str__(self):
return "%s:%d" % (self.filename, self.lineno)
# In case there are places where someone really expects a number
def __int__(self):
return self.lineno
####################### #######################
# #
# ISA Parser # ISA Parser
@ -1515,7 +1523,7 @@ class ISAParser(Grammar):
try: try:
t.value = int(t.value,0) t.value = int(t.value,0)
except ValueError: except ValueError:
error(t, 'Integer value "%s" too large' % t.value) error(t.lexer.lineno, 'Integer value "%s" too large' % t.value)
t.value = 0 t.value = 0
return t return t
@ -1544,13 +1552,13 @@ class ISAParser(Grammar):
return t return t
def t_NEWFILE(self, t): def t_NEWFILE(self, t):
r'^\#\#newfile\s+"[^"]*"' r'^\#\#newfile\s+"[^"]*"\n'
self.fileNameStack.push((t.value[11:-1], t.lexer.lineno)) self.fileNameStack.push(t.lexer.lineno)
t.lexer.lineno = 0 t.lexer.lineno = LineTracker(t.value[11:-2])
def t_ENDFILE(self, t): def t_ENDFILE(self, t):
r'^\#\#endfile' r'^\#\#endfile\n'
(old_filename, t.lexer.lineno) = self.fileNameStack.pop() t.lexer.lineno = self.fileNameStack.pop()
# #
# The functions t_NEWLINE, t_ignore, and t_error are # The functions t_NEWLINE, t_ignore, and t_error are
@ -1571,7 +1579,7 @@ class ISAParser(Grammar):
# Error handler # Error handler
def t_error(self, t): def t_error(self, t):
error(t, "illegal character '%s'" % t.value[0]) error(t.lexer.lineno, "illegal character '%s'" % t.value[0])
t.skip(1) t.skip(1)
##################################################################### #####################################################################
@ -1727,7 +1735,7 @@ del wrap
except Exception, exc: except Exception, exc:
if debug: if debug:
raise raise
error(t, 'error: %s in global let block "%s".' % (exc, t[2])) error(t.lineno(1), 'In global let block: %s' % exc)
GenCode(self, GenCode(self,
header_output=self.exportContext["header_output"], header_output=self.exportContext["header_output"],
decoder_output=self.exportContext["decoder_output"], decoder_output=self.exportContext["decoder_output"],
@ -1743,21 +1751,22 @@ del wrap
except Exception, exc: except Exception, exc:
if debug: if debug:
raise raise
error(t, error(t.lineno(1),
'error: %s in def operand_types block "%s".' % (exc, t[3])) 'In def operand_types: %s' % exc)
# Define the mapping from operand names to operand classes and # Define the mapping from operand names to operand classes and
# other traits. Stored in operandNameMap. # other traits. Stored in operandNameMap.
def p_def_operands(self, t): def p_def_operands(self, t):
'def_operands : DEF OPERANDS CODELIT SEMI' 'def_operands : DEF OPERANDS CODELIT SEMI'
if not hasattr(self, 'operandTypeMap'): if not hasattr(self, 'operandTypeMap'):
error(t, 'error: operand types must be defined before operands') error(t.lineno(1),
'error: operand types must be defined before operands')
try: try:
user_dict = eval('{' + t[3] + '}', self.exportContext) user_dict = eval('{' + t[3] + '}', self.exportContext)
except Exception, exc: except Exception, exc:
if debug: if debug:
raise raise
error(t, 'error: %s in def operands block "%s".' % (exc, t[3])) error(t.lineno(1), 'In def operands: %s' % exc)
self.buildOperandNameMap(user_dict, t.lexer.lineno) self.buildOperandNameMap(user_dict, t.lexer.lineno)
# A bitfield definition looks like: # A bitfield definition looks like:
@ -1784,7 +1793,8 @@ del wrap
def p_def_bitfield_struct(self, t): def p_def_bitfield_struct(self, t):
'def_bitfield_struct : DEF opt_signed BITFIELD ID id_with_dot SEMI' 'def_bitfield_struct : DEF opt_signed BITFIELD ID id_with_dot SEMI'
if (t[2] != ''): if (t[2] != ''):
error(t, 'error: structure bitfields are always unsigned.') error(t.lineno(1),
'error: structure bitfields are always unsigned.')
expr = 'machInst.%s' % t[5] expr = 'machInst.%s' % t[5]
hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr) hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr)
GenCode(self, header_output=hash_define).emit() GenCode(self, header_output=hash_define).emit()
@ -1938,7 +1948,7 @@ StaticInstPtr
def p_decode_stmt_list_1(self, t): def p_decode_stmt_list_1(self, t):
'decode_stmt_list : decode_stmt decode_stmt_list' 'decode_stmt_list : decode_stmt decode_stmt_list'
if (t[1].has_decode_default and t[2].has_decode_default): if (t[1].has_decode_default and t[2].has_decode_default):
error(t, 'Two default cases in decode block') error(t.lineno(1), 'Two default cases in decode block')
t[0] = t[1] + t[2] t[0] = t[1] + t[2]
# #
@ -1985,7 +1995,7 @@ StaticInstPtr
self.formatStack.push(self.formatMap[t[1]]) self.formatStack.push(self.formatMap[t[1]])
t[0] = ('', '// format %s' % t[1]) t[0] = ('', '// format %s' % t[1])
except KeyError: except KeyError:
error(t, 'instruction format "%s" not defined.' % t[1]) error(t.lineno(1), 'instruction format "%s" not defined.' % t[1])
# Nested decode block: if the value of the current field matches # Nested decode block: if the value of the current field matches
# the specified constant(s), do a nested decode on some other field. # the specified constant(s), do a nested decode on some other field.
@ -2065,7 +2075,7 @@ StaticInstPtr
try: try:
format = self.formatMap[t[1]] format = self.formatMap[t[1]]
except KeyError: except KeyError:
error(t, 'instruction format "%s" not defined.' % t[1]) error(t.lineno(1), 'instruction format "%s" not defined.' % t[1])
codeObj = format.defineInst(self, t[3], t[5], t.lexer.lineno) codeObj = format.defineInst(self, t[3], t[5], t.lexer.lineno)
comment = '\n// %s::%s(%s)\n' % (t[1], t[3], t[5]) comment = '\n// %s::%s(%s)\n' % (t[1], t[3], t[5])
@ -2158,7 +2168,7 @@ StaticInstPtr
# t.value) # t.value)
def p_error(self, t): def p_error(self, t):
if t: if t:
error(t, "syntax error at '%s'" % t.value) error(t.lexer.lineno, "syntax error at '%s'" % t.value)
else: else:
error("unknown syntax error") error("unknown syntax error")
@ -2362,7 +2372,7 @@ StaticInstPtr
except IOError: except IOError:
error('Error including file "%s"' % filename) error('Error including file "%s"' % filename)
self.fileNameStack.push((filename, 0)) self.fileNameStack.push(LineTracker(filename))
# Find any includes and include them # Find any includes and include them
def replace(matchobj): def replace(matchobj):
@ -2396,8 +2406,8 @@ StaticInstPtr
# do this up front. # do this up front.
isa_desc = self.read_and_flatten(isa_desc_file) isa_desc = self.read_and_flatten(isa_desc_file)
# Initialize filename stack with outer file. # Initialize lineno tracker
self.fileNameStack.push((isa_desc_file, 0)) self.lex.lineno = LineTracker(isa_desc_file)
# Parse. # Parse.
self.parse_string(isa_desc) self.parse_string(isa_desc)
@ -2408,7 +2418,10 @@ StaticInstPtr
try: try:
self._parse_isa_desc(*args, **kwargs) self._parse_isa_desc(*args, **kwargs)
except ISAParserError, e: except ISAParserError, e:
e.exit(self.fileNameStack) print backtrace(self.fileNameStack)
print "At %s:" % e.lineno
print e
sys.exit(1)
# Called as script: get args from command line. # Called as script: get args from command line.
# Args are: <isa desc file> <output dir> # Args are: <isa desc file> <output dir>