From f7a627338ccbe0ed2c3c96ee7b9ba4552de987ab Mon Sep 17 00:00:00 2001 From: Nathan Binkert Date: Fri, 26 Feb 2010 18:14:48 -0800 Subject: isa_parser: move code around to prepare for putting more stuff in the class --- src/arch/isa_parser.py | 3280 ++++++++++++++++++++++++------------------------ 1 file changed, 1639 insertions(+), 1641 deletions(-) diff --git a/src/arch/isa_parser.py b/src/arch/isa_parser.py index 42f51d806..08449235e 100755 --- a/src/arch/isa_parser.py +++ b/src/arch/isa_parser.py @@ -36,878 +36,1120 @@ from types import * from m5.util.grammar import Grammar -class ISAParser(Grammar): - def __init__(self, *args, **kwargs): - super(ISAParser, self).__init__(*args, **kwargs) - self.templateMap = {} +################### +# Utility functions - ##################################################################### - # - # Lexer - # - # The PLY lexer module takes two things as input: - # - A list of token names (the string list 'tokens') - # - A regular expression describing a match for each token. The - # regexp for token FOO can be provided in two ways: - # - as a string variable named t_FOO - # - as the doc string for a function named t_FOO. In this case, - # the function is also executed, allowing an action to be - # associated with each token match. - # - ##################################################################### +# +# Indent every line in string 's' by two spaces +# (except preprocessor directives). +# Used to make nested code blocks look pretty. +# +def indent(s): + return re.sub(r'(?m)^(?!#)', ' ', s) - # Reserved words. These are listed separately as they are matched - # using the same regexp as generic IDs, but distinguished in the - # t_ID() function. The PLY documentation suggests this approach. - reserved = ( - 'BITFIELD', 'DECODE', 'DECODER', 'DEFAULT', 'DEF', 'EXEC', 'FORMAT', - 'HEADER', 'LET', 'NAMESPACE', 'OPERAND_TYPES', 'OPERANDS', - 'OUTPUT', 'SIGNED', 'TEMPLATE' - ) +# +# Munge a somewhat arbitrarily formatted piece of Python code +# (e.g. from a format 'let' block) into something whose indentation +# will get by the Python parser. +# +# The two keys here are that Python will give a syntax error if +# there's any whitespace at the beginning of the first line, and that +# all lines at the same lexical nesting level must have identical +# indentation. Unfortunately the way code literals work, an entire +# let block tends to have some initial indentation. Rather than +# trying to figure out what that is and strip it off, we prepend 'if +# 1:' to make the let code the nested block inside the if (and have +# the parser automatically deal with the indentation for us). +# +# We don't want to do this if (1) the code block is empty or (2) the +# first line of the block doesn't have any whitespace at the front. - # List of tokens. The lex module requires this. - tokens = reserved + ( - # identifier - 'ID', +def fixPythonIndentation(s): + # get rid of blank lines first + s = re.sub(r'(?m)^\s*\n', '', s); + if (s != '' and re.match(r'[ \t]', s[0])): + s = 'if 1:\n' + s + return s - # integer literal - 'INTLIT', +# Error handler. Just call exit. Output formatted to work under +# Emacs compile-mode. Optional 'print_traceback' arg, if set to True, +# prints a Python stack backtrace too (can be handy when trying to +# debug the parser itself). +def error(lineno, string, print_traceback = False): + spaces = "" + for (filename, line) in fileNameStack[0:-1]: + print spaces + "In file included from " + filename + ":" + spaces += " " + # Print a Python stack backtrace if requested. + if (print_traceback): + traceback.print_exc() + if lineno != 0: + line_str = "%d:" % lineno + else: + line_str = "" + sys.exit(spaces + "%s:%s %s" % (fileNameStack[-1][0], line_str, string)) - # string literal - 'STRLIT', +#################### +# Template objects. +# +# Template objects are format strings that allow substitution from +# the attribute spaces of other objects (e.g. InstObjParams instances). - # code literal - 'CODELIT', +labelRE = re.compile(r'(? , ; . : :: * - 'LPAREN', 'RPAREN', - 'LBRACKET', 'RBRACKET', - 'LBRACE', 'RBRACE', - 'LESS', 'GREATER', 'EQUALS', - 'COMMA', 'SEMI', 'DOT', 'COLON', 'DBLCOLON', - 'ASTERISK', +class Template(object): + def __init__(self, t): + self.template = t - # C preprocessor directives - 'CPPDIRECTIVE' + def subst(self, d): + myDict = None - # The following are matched but never returned. commented out to - # suppress PLY warning - # newfile directive - # 'NEWFILE', + # Protect non-Python-dict substitutions (e.g. if there's a printf + # in the templated C++ code) + template = protect_non_subst_percents(self.template) + # CPU-model-specific substitutions are handled later (in GenCode). + template = protect_cpu_symbols(template) - # endfile directive - # 'ENDFILE' - ) + # Build a dict ('myDict') to use for the template substitution. + # Start with the template namespace. Make a copy since we're + # going to modify it. + myDict = parser.templateMap.copy() - # Regular expressions for token matching - t_LPAREN = r'\(' - t_RPAREN = r'\)' - t_LBRACKET = r'\[' - t_RBRACKET = r'\]' - t_LBRACE = r'\{' - t_RBRACE = r'\}' - t_LESS = r'\<' - t_GREATER = r'\>' - t_EQUALS = r'=' - t_COMMA = r',' - t_SEMI = r';' - t_DOT = r'\.' - t_COLON = r':' - t_DBLCOLON = r'::' - t_ASTERISK = r'\*' + if isinstance(d, InstObjParams): + # If we're dealing with an InstObjParams object, we need + # to be a little more sophisticated. The instruction-wide + # parameters are already formed, but the parameters which + # are only function wide still need to be generated. + compositeCode = '' - # Identifiers and reserved words - reserved_map = { } - for r in reserved: - reserved_map[r.lower()] = r + myDict.update(d.__dict__) + # The "operands" and "snippets" attributes of the InstObjParams + # objects are for internal use and not substitution. + del myDict['operands'] + del myDict['snippets'] - def t_ID(self, t): - r'[A-Za-z_]\w*' - t.type = self.reserved_map.get(t.value, 'ID') - return t + snippetLabels = [l for l in labelRE.findall(template) + if d.snippets.has_key(l)] - # Integer literal - def t_INTLIT(self, t): - r'-?(0x[\da-fA-F]+)|\d+' - try: - t.value = int(t.value,0) - except ValueError: - error(t.lexer.lineno, 'Integer value "%s" too large' % t.value) - t.value = 0 - return t + snippets = dict([(s, mungeSnippet(d.snippets[s])) + for s in snippetLabels]) - # String literal. Note that these use only single quotes, and - # can span multiple lines. - def t_STRLIT(self, t): - r"(?m)'([^'])+'" - # strip off quotes - t.value = t.value[1:-1] - t.lexer.lineno += t.value.count('\n') - return t + myDict.update(snippets) + compositeCode = ' '.join(map(str, snippets.values())) - # "Code literal"... like a string literal, but delimiters are - # '{{' and '}}' so they get formatted nicely under emacs c-mode - def t_CODELIT(self, t): - r"(?m)\{\{([^\}]|}(?!\}))+\}\}" - # strip off {{ & }} - t.value = t.value[2:-2] - t.lexer.lineno += t.value.count('\n') - return t + # Add in template itself in case it references any + # operands explicitly (like Mem) + compositeCode += ' ' + template - def t_CPPDIRECTIVE(self, t): - r'^\#[^\#].*\n' - t.lexer.lineno += t.value.count('\n') - return t + operands = SubOperandList(compositeCode, d.operands) - def t_NEWFILE(self, t): - r'^\#\#newfile\s+"[\w/.-]*"' - fileNameStack.push((t.value[11:-1], t.lexer.lineno)) - t.lexer.lineno = 0 + myDict['op_decl'] = operands.concatAttrStrings('op_decl') - def t_ENDFILE(self, t): - r'^\#\#endfile' - (old_filename, t.lexer.lineno) = fileNameStack.pop() + is_src = lambda op: op.is_src + is_dest = lambda op: op.is_dest - # - # The functions t_NEWLINE, t_ignore, and t_error are - # special for the lex module. - # + myDict['op_src_decl'] = \ + operands.concatSomeAttrStrings(is_src, 'op_src_decl') + myDict['op_dest_decl'] = \ + operands.concatSomeAttrStrings(is_dest, 'op_dest_decl') - # Newlines - def t_NEWLINE(self, t): - r'\n+' - t.lexer.lineno += t.value.count('\n') + myDict['op_rd'] = operands.concatAttrStrings('op_rd') + myDict['op_wb'] = operands.concatAttrStrings('op_wb') - # Comments - def t_comment(self, t): - r'//.*' + if d.operands.memOperand: + myDict['mem_acc_size'] = d.operands.memOperand.mem_acc_size + myDict['mem_acc_type'] = d.operands.memOperand.mem_acc_type - # Completely ignored characters - t_ignore = ' \t\x0c' + elif isinstance(d, dict): + # if the argument is a dictionary, we just use it. + myDict.update(d) + elif hasattr(d, '__dict__'): + # if the argument is an object, we use its attribute map. + myDict.update(d.__dict__) + else: + raise TypeError, "Template.subst() arg must be or have dictionary" + return template % myDict - # Error handler - def t_error(self, t): - error(t.lexer.lineno, "illegal character '%s'" % t.value[0]) - t.skip(1) + # Convert to string. This handles the case when a template with a + # CPU-specific term gets interpolated into another template or into + # an output block. + def __str__(self): + return expand_cpu_symbols_to_string(self.template) - ##################################################################### - # - # Parser - # - # Every function whose name starts with 'p_' defines a grammar - # rule. The rule is encoded in the function's doc string, while - # the function body provides the action taken when the rule is - # matched. The argument to each function is a list of the values - # of the rule's symbols: t[0] for the LHS, and t[1..n] for the - # symbols on the RHS. For tokens, the value is copied from the - # t.value attribute provided by the lexer. For non-terminals, the - # value is assigned by the producing rule; i.e., the job of the - # grammar rule function is to set the value for the non-terminal - # on the LHS (by assigning to t[0]). - ##################################################################### - - # The LHS of the first grammar rule is used as the start symbol - # (in this case, 'specification'). Note that this rule enforces - # that there will be exactly one namespace declaration, with 0 or - # more global defs/decls before and after it. The defs & decls - # before the namespace decl will be outside the namespace; those - # after will be inside. The decoder function is always inside the - # namespace. - def p_specification(self, t): - 'specification : opt_defs_and_outputs name_decl opt_defs_and_outputs decode_block' - global_code = t[1] - isa_name = t[2] - namespace = isa_name + "Inst" - # wrap the decode block as a function definition - t[4].wrap_decode_block(''' -StaticInstPtr -%(isa_name)s::decodeInst(%(isa_name)s::ExtMachInst machInst) -{ - using namespace %(namespace)s; -''' % vars(), '}') - # both the latter output blocks and the decode block are in - # the namespace - namespace_code = t[3] + t[4] - # pass it all back to the caller of yacc.parse() - t[0] = (isa_name, namespace, global_code, namespace_code) - - # ISA name declaration looks like "namespace ;" - def p_name_decl(self, t): - 'name_decl : NAMESPACE ID SEMI' - t[0] = t[2] - - # 'opt_defs_and_outputs' is a possibly empty sequence of - # def and/or output statements. - def p_opt_defs_and_outputs_0(self, t): - 'opt_defs_and_outputs : empty' - t[0] = GenCode() - - def p_opt_defs_and_outputs_1(self, t): - 'opt_defs_and_outputs : defs_and_outputs' - t[0] = t[1] - - def p_defs_and_outputs_0(self, t): - 'defs_and_outputs : def_or_output' - t[0] = t[1] - - def p_defs_and_outputs_1(self, t): - 'defs_and_outputs : defs_and_outputs def_or_output' - t[0] = t[1] + t[2] +################ +# Format object. +# +# A format object encapsulates an instruction format. It must provide +# a defineInst() method that generates the code for an instruction +# definition. - # The list of possible definition/output statements. - def p_def_or_output(self, t): - '''def_or_output : def_format - | def_bitfield - | def_bitfield_struct - | def_template - | def_operand_types - | def_operands - | output_header - | output_decoder - | output_exec - | global_let''' - t[0] = t[1] +exportContextSymbols = ('InstObjParams', 'makeList', 're', 'string') - # Output blocks 'output {{...}}' (C++ code blocks) are copied - # directly to the appropriate output section. +exportContext = {} - # Massage output block by substituting in template definitions and - # bit operators. We handle '%'s embedded in the string that don't - # indicate template substitutions (or CPU-specific symbols, which - # get handled in GenCode) by doubling them first so that the - # format operation will reduce them back to single '%'s. - def process_output(self, s): - s = protect_non_subst_percents(s) - # protects cpu-specific symbols too - s = protect_cpu_symbols(s) - return substBitOps(s % self.templateMap) +def updateExportContext(): + exportContext.update(exportDict(*exportContextSymbols)) + exportContext.update(parser.templateMap) - def p_output_header(self, t): - 'output_header : OUTPUT HEADER CODELIT SEMI' - t[0] = GenCode(header_output = self.process_output(t[3])) +def exportDict(*symNames): + return dict([(s, eval(s)) for s in symNames]) - def p_output_decoder(self, t): - 'output_decoder : OUTPUT DECODER CODELIT SEMI' - t[0] = GenCode(decoder_output = self.process_output(t[3])) - def p_output_exec(self, t): - 'output_exec : OUTPUT EXEC CODELIT SEMI' - t[0] = GenCode(exec_output = self.process_output(t[3])) +class Format(object): + def __init__(self, id, params, code): + # constructor: just save away arguments + self.id = id + self.params = params + label = 'def format ' + id + self.user_code = compile(fixPythonIndentation(code), label, 'exec') + param_list = string.join(params, ", ") + f = '''def defInst(_code, _context, %s): + my_locals = vars().copy() + exec _code in _context, my_locals + return my_locals\n''' % param_list + c = compile(f, label + ' wrapper', 'exec') + exec c + self.func = defInst - # global let blocks 'let {{...}}' (Python code blocks) are - # executed directly when seen. Note that these execute in a - # special variable context 'exportContext' to prevent the code - # from polluting this script's namespace. - def p_global_let(self, t): - 'global_let : LET CODELIT SEMI' + def defineInst(self, name, args, lineno): + context = {} updateExportContext() - exportContext["header_output"] = '' - exportContext["decoder_output"] = '' - exportContext["exec_output"] = '' - exportContext["decode_block"] = '' + context.update(exportContext) + if len(name): + Name = name[0].upper() + if len(name) > 1: + Name += name[1:] + context.update({ 'name': name, 'Name': Name }) try: - exec fixPythonIndentation(t[2]) in exportContext + vars = self.func(self.user_code, context, *args[0], **args[1]) except Exception, exc: - error(t.lexer.lineno, - 'error: %s in global let block "%s".' % (exc, t[2])) - t[0] = GenCode(header_output = exportContext["header_output"], - decoder_output = exportContext["decoder_output"], - exec_output = exportContext["exec_output"], - decode_block = exportContext["decode_block"]) + error(lineno, 'error defining "%s": %s.' % (name, exc)) + for k in vars.keys(): + if k not in ('header_output', 'decoder_output', + 'exec_output', 'decode_block'): + del vars[k] + return GenCode(**vars) - # Define the mapping from operand type extensions to C++ types and - # bit widths (stored in operandTypeMap). - def p_def_operand_types(self, t): - 'def_operand_types : DEF OPERAND_TYPES CODELIT SEMI' - try: - user_dict = eval('{' + t[3] + '}') - except Exception, exc: - error(t.lexer.lineno, - 'error: %s in def operand_types block "%s".' % (exc, t[3])) - buildOperandTypeMap(user_dict, t.lexer.lineno) - t[0] = GenCode() # contributes nothing to the output C++ file +# Special null format to catch an implicit-format instruction +# definition outside of any format block. +class NoFormat(object): + def __init__(self): + self.defaultInst = '' - # Define the mapping from operand names to operand classes and - # other traits. Stored in operandNameMap. - def p_def_operands(self, t): - 'def_operands : DEF OPERANDS CODELIT SEMI' - if not globals().has_key('operandTypeMap'): - error(t.lexer.lineno, - 'error: operand types must be defined before operands') - try: - user_dict = eval('{' + t[3] + '}', exportContext) - except Exception, exc: - error(t.lexer.lineno, - 'error: %s in def operands block "%s".' % (exc, t[3])) - buildOperandNameMap(user_dict, t.lexer.lineno) - t[0] = GenCode() # contributes nothing to the output C++ file + def defineInst(self, name, args, lineno): + error(lineno, + 'instruction definition "%s" with no active format!' % name) - # A bitfield definition looks like: - # 'def [signed] bitfield [:]' - # This generates a preprocessor macro in the output file. - def p_def_bitfield_0(self, t): - 'def_bitfield : DEF opt_signed BITFIELD ID LESS INTLIT COLON INTLIT GREATER SEMI' - expr = 'bits(machInst, %2d, %2d)' % (t[6], t[8]) - if (t[2] == 'signed'): - expr = 'sext<%d>(%s)' % (t[6] - t[8] + 1, expr) - hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr) - t[0] = GenCode(header_output = hash_define) +# This dictionary maps format name strings to Format objects. +formatMap = {} - # alternate form for single bit: 'def [signed] bitfield []' - def p_def_bitfield_1(self, t): - 'def_bitfield : DEF opt_signed BITFIELD ID LESS INTLIT GREATER SEMI' - expr = 'bits(machInst, %2d, %2d)' % (t[6], t[6]) - if (t[2] == 'signed'): - expr = 'sext<%d>(%s)' % (1, expr) - hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr) - t[0] = GenCode(header_output = hash_define) +# Define a new format +def defFormat(id, params, code, lineno): + # make sure we haven't already defined this one + if formatMap.get(id, None) != None: + error(lineno, 'format %s redefined.' % id) + # create new object and store in global map + formatMap[id] = Format(id, params, code) - # alternate form for structure member: 'def bitfield ' - def p_def_bitfield_struct(self, t): - 'def_bitfield_struct : DEF opt_signed BITFIELD ID id_with_dot SEMI' - if (t[2] != ''): - error(t.lexer.lineno, - 'error: structure bitfields are always unsigned.') - expr = 'machInst.%s' % t[5] - hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr) - t[0] = GenCode(header_output = hash_define) +##################################################################### +# +# Support Classes +# +##################################################################### - def p_id_with_dot_0(self, t): - 'id_with_dot : ID' - t[0] = t[1] +# Expand template with CPU-specific references into a dictionary with +# an entry for each CPU model name. The entry key is the model name +# and the corresponding value is the template with the CPU-specific +# refs substituted for that model. +def expand_cpu_symbols_to_dict(template): + # Protect '%'s that don't go with CPU-specific terms + t = re.sub(r'%(?!\(CPU_)', '%%', template) + result = {} + for cpu in cpu_models: + result[cpu.name] = t % cpu.strings + return result - def p_id_with_dot_1(self, t): - 'id_with_dot : ID DOT id_with_dot' - t[0] = t[1] + t[2] + t[3] +# *If* the template has CPU-specific references, return a single +# string containing a copy of the template for each CPU model with the +# corresponding values substituted in. If the template has no +# CPU-specific references, it is returned unmodified. +def expand_cpu_symbols_to_string(template): + if template.find('%(CPU_') != -1: + return reduce(lambda x,y: x+y, + expand_cpu_symbols_to_dict(template).values()) + else: + return template - def p_opt_signed_0(self, t): - 'opt_signed : SIGNED' - t[0] = t[1] +# Protect CPU-specific references by doubling the corresponding '%'s +# (in preparation for substituting a different set of references into +# the template). +def protect_cpu_symbols(template): + return re.sub(r'%(?=\(CPU_)', '%%', template) - def p_opt_signed_1(self, t): - 'opt_signed : empty' - t[0] = '' - - def p_def_template(self, t): - 'def_template : DEF TEMPLATE ID CODELIT SEMI' - self.templateMap[t[3]] = Template(t[4]) - t[0] = GenCode() +# Protect any non-dict-substitution '%'s in a format string +# (i.e. those not followed by '(') +def protect_non_subst_percents(s): + return re.sub(r'%(?!\()', '%%', s) - # An instruction format definition looks like - # "def format () {{...}};" - def p_def_format(self, t): - 'def_format : DEF FORMAT ID LPAREN param_list RPAREN CODELIT SEMI' - (id, params, code) = (t[3], t[5], t[7]) - defFormat(id, params, code, t.lexer.lineno) - t[0] = GenCode() +############### +# GenCode class +# +# The GenCode class encapsulates generated code destined for various +# output files. The header_output and decoder_output attributes are +# strings containing code destined for decoder.hh and decoder.cc +# respectively. The decode_block attribute contains code to be +# incorporated in the decode function itself (that will also end up in +# decoder.cc). The exec_output attribute is a dictionary with a key +# for each CPU model name; the value associated with a particular key +# is the string of code for that CPU model's exec.cc file. The +# has_decode_default attribute is used in the decode block to allow +# explicit default clauses to override default default clauses. - # The formal parameter list for an instruction format is a - # possibly empty list of comma-separated parameters. Positional - # (standard, non-keyword) parameters must come first, followed by - # keyword parameters, followed by a '*foo' parameter that gets - # excess positional arguments (as in Python). Each of these three - # parameter categories is optional. - # - # Note that we do not support the '**foo' parameter for collecting - # otherwise undefined keyword args. Otherwise the parameter list - # is (I believe) identical to what is supported in Python. - # - # The param list generates a tuple, where the first element is a - # list of the positional params and the second element is a dict - # containing the keyword params. - def p_param_list_0(self, t): - 'param_list : positional_param_list COMMA nonpositional_param_list' - t[0] = t[1] + t[3] +class GenCode(object): + # Constructor. At this point we substitute out all CPU-specific + # symbols. For the exec output, these go into the per-model + # dictionary. For all other output types they get collapsed into + # a single string. + def __init__(self, + header_output = '', decoder_output = '', exec_output = '', + decode_block = '', has_decode_default = False): + self.header_output = expand_cpu_symbols_to_string(header_output) + self.decoder_output = expand_cpu_symbols_to_string(decoder_output) + if isinstance(exec_output, dict): + self.exec_output = exec_output + elif isinstance(exec_output, str): + # If the exec_output arg is a single string, we replicate + # it for each of the CPU models, substituting and + # %(CPU_foo)s params appropriately. + self.exec_output = expand_cpu_symbols_to_dict(exec_output) + self.decode_block = expand_cpu_symbols_to_string(decode_block) + self.has_decode_default = has_decode_default - def p_param_list_1(self, t): - '''param_list : positional_param_list - | nonpositional_param_list''' - t[0] = t[1] + # Override '+' operator: generate a new GenCode object that + # concatenates all the individual strings in the operands. + def __add__(self, other): + exec_output = {} + for cpu in cpu_models: + n = cpu.name + exec_output[n] = self.exec_output[n] + other.exec_output[n] + return GenCode(self.header_output + other.header_output, + self.decoder_output + other.decoder_output, + exec_output, + self.decode_block + other.decode_block, + self.has_decode_default or other.has_decode_default) - def p_positional_param_list_0(self, t): - 'positional_param_list : empty' - t[0] = [] + # Prepend a string (typically a comment) to all the strings. + def prepend_all(self, pre): + self.header_output = pre + self.header_output + self.decoder_output = pre + self.decoder_output + self.decode_block = pre + self.decode_block + for cpu in cpu_models: + self.exec_output[cpu.name] = pre + self.exec_output[cpu.name] - def p_positional_param_list_1(self, t): - 'positional_param_list : ID' - t[0] = [t[1]] + # Wrap the decode block in a pair of strings (e.g., 'case foo:' + # and 'break;'). Used to build the big nested switch statement. + def wrap_decode_block(self, pre, post = ''): + self.decode_block = pre + indent(self.decode_block) + post - def p_positional_param_list_2(self, t): - 'positional_param_list : positional_param_list COMMA ID' - t[0] = t[1] + [t[3]] +##################################################################### +# +# Bitfield Operator Support +# +##################################################################### - def p_nonpositional_param_list_0(self, t): - 'nonpositional_param_list : keyword_param_list COMMA excess_args_param' - t[0] = t[1] + t[3] +bitOp1ArgRE = re.compile(r'<\s*(\w+)\s*:\s*>') - def p_nonpositional_param_list_1(self, t): - '''nonpositional_param_list : keyword_param_list - | excess_args_param''' - t[0] = t[1] +bitOpWordRE = re.compile(r'(?') +bitOpExprRE = re.compile(r'\)<\s*(\w+)\s*:\s*(\w+)\s*>') - def p_keyword_param_list_0(self, t): - 'keyword_param_list : keyword_param' - t[0] = [t[1]] +def substBitOps(code): + # first convert single-bit selectors to two-index form + # i.e., --> + code = bitOp1ArgRE.sub(r'<\1:\1>', code) + # simple case: selector applied to ID (name) + # i.e., foo --> bits(foo, a, b) + code = bitOpWordRE.sub(r'bits(\1, \2, \3)', code) + # if selector is applied to expression (ending in ')'), + # we need to search backward for matching '(' + match = bitOpExprRE.search(code) + while match: + exprEnd = match.start() + here = exprEnd - 1 + nestLevel = 1 + while nestLevel > 0: + if code[here] == '(': + nestLevel -= 1 + elif code[here] == ')': + nestLevel += 1 + here -= 1 + if here < 0: + sys.exit("Didn't find '('!") + exprStart = here+1 + newExpr = r'bits(%s, %s, %s)' % (code[exprStart:exprEnd+1], + match.group(1), match.group(2)) + code = code[:exprStart] + newExpr + code[match.end():] + match = bitOpExprRE.search(code) + return code - def p_keyword_param_list_1(self, t): - 'keyword_param_list : keyword_param_list COMMA keyword_param' - t[0] = t[1] + [t[3]] - def p_keyword_param(self, t): - 'keyword_param : ID EQUALS expr' - t[0] = t[1] + ' = ' + t[3].__repr__() +##################################################################### +# +# Code Parser +# +# The remaining code is the support for automatically extracting +# instruction characteristics from pseudocode. +# +##################################################################### - def p_excess_args_param(self, t): - 'excess_args_param : ASTERISK ID' - # Just concatenate them: '*ID'. Wrap in list to be consistent - # with positional_param_list and keyword_param_list. - t[0] = [t[1] + t[2]] +# Force the argument to be a list. Useful for flags, where a caller +# can specify a singleton flag or a list of flags. Also usful for +# converting tuples to lists so they can be modified. +def makeList(arg): + if isinstance(arg, list): + return arg + elif isinstance(arg, tuple): + return list(arg) + elif not arg: + return [] + else: + return [ arg ] - # End of format definition-related rules. - ############## +# Generate operandTypeMap from the user's 'def operand_types' +# statement. +def buildOperandTypeMap(user_dict, lineno): + global operandTypeMap + operandTypeMap = {} + for (ext, (desc, size)) in user_dict.iteritems(): + if desc == 'signed int': + ctype = 'int%d_t' % size + is_signed = 1 + elif desc == 'unsigned int': + ctype = 'uint%d_t' % size + is_signed = 0 + elif desc == 'float': + is_signed = 1 # shouldn't really matter + if size == 32: + ctype = 'float' + elif size == 64: + ctype = 'double' + elif desc == 'twin64 int': + is_signed = 0 + ctype = 'Twin64_t' + elif desc == 'twin32 int': + is_signed = 0 + ctype = 'Twin32_t' + if ctype == '': + error(lineno, 'Unrecognized type description "%s" in user_dict') + operandTypeMap[ext] = (size, ctype, is_signed) - # - # A decode block looks like: - # decode [, ]* [default ] { ... } - # - def p_decode_block(self, t): - 'decode_block : DECODE ID opt_default LBRACE decode_stmt_list RBRACE' - default_defaults = defaultStack.pop() - codeObj = t[5] - # use the "default defaults" only if there was no explicit - # default statement in decode_stmt_list - if not codeObj.has_decode_default: - codeObj += default_defaults - codeObj.wrap_decode_block('switch (%s) {\n' % t[2], '}\n') - t[0] = codeObj +class Operand(object): + '''Base class for operand descriptors. An instance of this class + (or actually a class derived from this one) represents a specific + operand for a code block (e.g, "Rc.sq" as a dest). Intermediate + derived classes encapsulates the traits of a particular operand + type (e.g., "32-bit integer register").''' - # The opt_default statement serves only to push the "default - # defaults" onto defaultStack. This value will be used by nested - # decode blocks, and used and popped off when the current - # decode_block is processed (in p_decode_block() above). - def p_opt_default_0(self, t): - 'opt_default : empty' - # no default specified: reuse the one currently at the top of - # the stack - defaultStack.push(defaultStack.top()) - # no meaningful value returned - t[0] = None + def buildReadCode(self, func = None): + code = self.read_code % {"name": self.base_name, + "func": func, + "op_idx": self.src_reg_idx, + "reg_idx": self.reg_spec, + "size": self.size, + "ctype": self.ctype} + if self.size != self.dflt_size: + return '%s = bits(%s, %d, 0);\n' % \ + (self.base_name, code, self.size-1) + else: + return '%s = %s;\n' % \ + (self.base_name, code) - def p_opt_default_1(self, t): - 'opt_default : DEFAULT inst' - # push the new default - codeObj = t[2] - codeObj.wrap_decode_block('\ndefault:\n', 'break;\n') - defaultStack.push(codeObj) - # no meaningful value returned - t[0] = None - - def p_decode_stmt_list_0(self, t): - 'decode_stmt_list : decode_stmt' - t[0] = t[1] - - def p_decode_stmt_list_1(self, t): - 'decode_stmt_list : decode_stmt decode_stmt_list' - if (t[1].has_decode_default and t[2].has_decode_default): - error(t.lexer.lineno, 'Two default cases in decode block') - t[0] = t[1] + t[2] - - # - # Decode statement rules - # - # There are four types of statements allowed in a decode block: - # 1. Format blocks 'format { ... }' - # 2. Nested decode blocks - # 3. Instruction definitions. - # 4. C preprocessor directives. - - - # Preprocessor directives found in a decode statement list are - # passed through to the output, replicated to all of the output - # code streams. This works well for ifdefs, so we can ifdef out - # both the declarations and the decode cases generated by an - # instruction definition. Handling them as part of the grammar - # makes it easy to keep them in the right place with respect to - # the code generated by the other statements. - def p_decode_stmt_cpp(self, t): - 'decode_stmt : CPPDIRECTIVE' - t[0] = GenCode(t[1], t[1], t[1], t[1]) - - # A format block 'format { ... }' sets the default - # instruction format used to handle instruction definitions inside - # the block. This format can be overridden by using an explicit - # format on the instruction definition or with a nested format - # block. - def p_decode_stmt_format(self, t): - 'decode_stmt : FORMAT push_format_id LBRACE decode_stmt_list RBRACE' - # The format will be pushed on the stack when 'push_format_id' - # is processed (see below). Once the parser has recognized - # the full production (though the right brace), we're done - # with the format, so now we can pop it. - formatStack.pop() - t[0] = t[4] - - # This rule exists so we can set the current format (& push the - # stack) when we recognize the format name part of the format - # block. - def p_push_format_id(self, t): - 'push_format_id : ID' - try: - formatStack.push(formatMap[t[1]]) - t[0] = ('', '// format %s' % t[1]) - except KeyError: - error(t.lexer.lineno, - 'instruction format "%s" not defined.' % t[1]) + def buildWriteCode(self, func = None): + if (self.size != self.dflt_size and self.is_signed): + final_val = 'sext<%d>(%s)' % (self.size, self.base_name) + else: + final_val = self.base_name + code = self.write_code % {"name": self.base_name, + "func": func, + "op_idx": self.dest_reg_idx, + "reg_idx": self.reg_spec, + "size": self.size, + "ctype": self.ctype, + "final_val": final_val} + return ''' + { + %s final_val = %s; + %s; + if (traceData) { traceData->setData(final_val); } + }''' % (self.dflt_ctype, final_val, code) - # Nested decode block: if the value of the current field matches - # the specified constant, do a nested decode on some other field. - def p_decode_stmt_decode(self, t): - 'decode_stmt : case_label COLON decode_block' - label = t[1] - codeObj = t[3] - # just wrap the decoding code from the block as a case in the - # outer switch statement. - codeObj.wrap_decode_block('\n%s:\n' % label) - codeObj.has_decode_default = (label == 'default') - t[0] = codeObj + def __init__(self, full_name, ext, is_src, is_dest): + self.full_name = full_name + self.ext = ext + self.is_src = is_src + self.is_dest = is_dest + # The 'effective extension' (eff_ext) is either the actual + # extension, if one was explicitly provided, or the default. + if ext: + self.eff_ext = ext + else: + self.eff_ext = self.dflt_ext - # Instruction definition (finally!). - def p_decode_stmt_inst(self, t): - 'decode_stmt : case_label COLON inst SEMI' - label = t[1] - codeObj = t[3] - codeObj.wrap_decode_block('\n%s:' % label, 'break;\n') - codeObj.has_decode_default = (label == 'default') - t[0] = codeObj + (self.size, self.ctype, self.is_signed) = operandTypeMap[self.eff_ext] - # The case label is either a list of one or more constants or - # 'default' - def p_case_label_0(self, t): - 'case_label : intlit_list' - def make_case(intlit): - if intlit >= 2**32: - return 'case ULL(%#x)' % intlit + # note that mem_acc_size is undefined for non-mem operands... + # template must be careful not to use it if it doesn't apply. + if self.isMem(): + self.mem_acc_size = self.makeAccSize() + if self.ctype in ['Twin32_t', 'Twin64_t']: + self.mem_acc_type = 'Twin' else: - return 'case %#x' % intlit - t[0] = ': '.join(map(make_case, t[1])) - - def p_case_label_1(self, t): - 'case_label : DEFAULT' - t[0] = 'default' + self.mem_acc_type = 'uint' - # - # The constant list for a decode case label must be non-empty, but - # may have one or more comma-separated integer literals in it. - # - def p_intlit_list_0(self, t): - 'intlit_list : INTLIT' - t[0] = [t[1]] + # Finalize additional fields (primarily code fields). This step + # is done separately since some of these fields may depend on the + # register index enumeration that hasn't been performed yet at the + # time of __init__(). + def finalize(self): + self.flags = self.getFlags() + self.constructor = self.makeConstructor() + self.op_decl = self.makeDecl() - def p_intlit_list_1(self, t): - 'intlit_list : intlit_list COMMA INTLIT' - t[0] = t[1] - t[0].append(t[3]) + if self.is_src: + self.op_rd = self.makeRead() + self.op_src_decl = self.makeDecl() + else: + self.op_rd = '' + self.op_src_decl = '' - # Define an instruction using the current instruction format - # (specified by an enclosing format block). - # "()" - def p_inst_0(self, t): - 'inst : ID LPAREN arg_list RPAREN' - # Pass the ID and arg list to the current format class to deal with. - currentFormat = formatStack.top() - codeObj = currentFormat.defineInst(t[1], t[3], t.lexer.lineno) - args = ','.join(map(str, t[3])) - args = re.sub('(?m)^', '//', args) - args = re.sub('^//', '', args) - comment = '\n// %s::%s(%s)\n' % (currentFormat.id, t[1], args) - codeObj.prepend_all(comment) - t[0] = codeObj + if self.is_dest: + self.op_wb = self.makeWrite() + self.op_dest_decl = self.makeDecl() + else: + self.op_wb = '' + self.op_dest_decl = '' - # Define an instruction using an explicitly specified format: - # "::()" - def p_inst_1(self, t): - 'inst : ID DBLCOLON ID LPAREN arg_list RPAREN' - try: - format = formatMap[t[1]] - except KeyError: - error(t.lexer.lineno, - 'instruction format "%s" not defined.' % t[1]) - codeObj = format.defineInst(t[3], t[5], t.lexer.lineno) - comment = '\n// %s::%s(%s)\n' % (t[1], t[3], t[5]) - codeObj.prepend_all(comment) - t[0] = codeObj + def isMem(self): + return 0 - # The arg list generates a tuple, where the first element is a - # list of the positional args and the second element is a dict - # containing the keyword args. - def p_arg_list_0(self, t): - 'arg_list : positional_arg_list COMMA keyword_arg_list' - t[0] = ( t[1], t[3] ) + def isReg(self): + return 0 - def p_arg_list_1(self, t): - 'arg_list : positional_arg_list' - t[0] = ( t[1], {} ) + def isFloatReg(self): + return 0 - def p_arg_list_2(self, t): - 'arg_list : keyword_arg_list' - t[0] = ( [], t[1] ) + def isIntReg(self): + return 0 - def p_positional_arg_list_0(self, t): - 'positional_arg_list : empty' - t[0] = [] + def isControlReg(self): + return 0 - def p_positional_arg_list_1(self, t): - 'positional_arg_list : expr' - t[0] = [t[1]] + def getFlags(self): + # note the empty slice '[:]' gives us a copy of self.flags[0] + # instead of a reference to it + my_flags = self.flags[0][:] + if self.is_src: + my_flags += self.flags[1] + if self.is_dest: + my_flags += self.flags[2] + return my_flags - def p_positional_arg_list_2(self, t): - 'positional_arg_list : positional_arg_list COMMA expr' - t[0] = t[1] + [t[3]] + def makeDecl(self): + # Note that initializations in the declarations are solely + # to avoid 'uninitialized variable' errors from the compiler. + return self.ctype + ' ' + self.base_name + ' = 0;\n'; - def p_keyword_arg_list_0(self, t): - 'keyword_arg_list : keyword_arg' - t[0] = t[1] +class IntRegOperand(Operand): + def isReg(self): + return 1 - def p_keyword_arg_list_1(self, t): - 'keyword_arg_list : keyword_arg_list COMMA keyword_arg' - t[0] = t[1] - t[0].update(t[3]) + def isIntReg(self): + return 1 - def p_keyword_arg(self, t): - 'keyword_arg : ID EQUALS expr' - t[0] = { t[1] : t[3] } + def makeConstructor(self): + c = '' + if self.is_src: + c += '\n\t_srcRegIdx[%d] = %s;' % \ + (self.src_reg_idx, self.reg_spec) + if self.is_dest: + c += '\n\t_destRegIdx[%d] = %s;' % \ + (self.dest_reg_idx, self.reg_spec) + return c - # - # Basic expressions. These constitute the argument values of - # "function calls" (i.e. instruction definitions in the decode - # block) and default values for formal parameters of format - # functions. - # - # Right now, these are either strings, integers, or (recursively) - # lists of exprs (using Python square-bracket list syntax). Note - # that bare identifiers are trated as string constants here (since - # there isn't really a variable namespace to refer to). - # - def p_expr_0(self, t): - '''expr : ID - | INTLIT - | STRLIT - | CODELIT''' - t[0] = t[1] + def makeRead(self): + if (self.ctype == 'float' or self.ctype == 'double'): + error(0, 'Attempt to read integer register as FP') + if self.read_code != None: + return self.buildReadCode('readIntRegOperand') + if (self.size == self.dflt_size): + return '%s = xc->readIntRegOperand(this, %d);\n' % \ + (self.base_name, self.src_reg_idx) + elif (self.size > self.dflt_size): + int_reg_val = 'xc->readIntRegOperand(this, %d)' % \ + (self.src_reg_idx) + if (self.is_signed): + int_reg_val = 'sext<%d>(%s)' % (self.dflt_size, int_reg_val) + return '%s = %s;\n' % (self.base_name, int_reg_val) + else: + return '%s = bits(xc->readIntRegOperand(this, %d), %d, 0);\n' % \ + (self.base_name, self.src_reg_idx, self.size-1) - def p_expr_1(self, t): - '''expr : LBRACKET list_expr RBRACKET''' - t[0] = t[2] + def makeWrite(self): + if (self.ctype == 'float' or self.ctype == 'double'): + error(0, 'Attempt to write integer register as FP') + if self.write_code != None: + return self.buildWriteCode('setIntRegOperand') + if (self.size != self.dflt_size and self.is_signed): + final_val = 'sext<%d>(%s)' % (self.size, self.base_name) + else: + final_val = self.base_name + wb = ''' + { + %s final_val = %s; + xc->setIntRegOperand(this, %d, final_val);\n + if (traceData) { traceData->setData(final_val); } + }''' % (self.dflt_ctype, final_val, self.dest_reg_idx) + return wb - def p_list_expr_0(self, t): - 'list_expr : expr' - t[0] = [t[1]] +class FloatRegOperand(Operand): + def isReg(self): + return 1 - def p_list_expr_1(self, t): - 'list_expr : list_expr COMMA expr' - t[0] = t[1] + [t[3]] + def isFloatReg(self): + return 1 - def p_list_expr_2(self, t): - 'list_expr : empty' - t[0] = [] + def makeConstructor(self): + c = '' + if self.is_src: + c += '\n\t_srcRegIdx[%d] = %s + FP_Base_DepTag;' % \ + (self.src_reg_idx, self.reg_spec) + if self.is_dest: + c += '\n\t_destRegIdx[%d] = %s + FP_Base_DepTag;' % \ + (self.dest_reg_idx, self.reg_spec) + return c - # - # Empty production... use in other rules for readability. - # - def p_empty(self, t): - 'empty :' - pass + def makeRead(self): + bit_select = 0 + if (self.ctype == 'float' or self.ctype == 'double'): + func = 'readFloatRegOperand' + else: + func = 'readFloatRegOperandBits' + if (self.size != self.dflt_size): + bit_select = 1 + base = 'xc->%s(this, %d)' % (func, self.src_reg_idx) + if self.read_code != None: + return self.buildReadCode(func) + if bit_select: + return '%s = bits(%s, %d, 0);\n' % \ + (self.base_name, base, self.size-1) + else: + return '%s = %s;\n' % (self.base_name, base) - # Parse error handler. Note that the argument here is the - # offending *token*, not a grammar symbol (hence the need to use - # t.value) - def p_error(self, t): - if t: - error(t.lexer.lineno, "syntax error at '%s'" % t.value) + def makeWrite(self): + final_val = self.base_name + final_ctype = self.ctype + if (self.ctype == 'float' or self.ctype == 'double'): + func = 'setFloatRegOperand' + elif (self.ctype == 'uint32_t' or self.ctype == 'uint64_t'): + func = 'setFloatRegOperandBits' else: - error(0, "unknown syntax error", True) + func = 'setFloatRegOperandBits' + final_ctype = 'uint%d_t' % self.dflt_size + if (self.size != self.dflt_size and self.is_signed): + final_val = 'sext<%d>(%s)' % (self.size, self.base_name) + if self.write_code != None: + return self.buildWriteCode(func) + wb = ''' + { + %s final_val = %s; + xc->%s(this, %d, final_val);\n + if (traceData) { traceData->setData(final_val); } + }''' % (final_ctype, final_val, func, self.dest_reg_idx) + return wb - # END OF GRAMMAR RULES +class ControlRegOperand(Operand): + def isReg(self): + return 1 -# Now build the parser. -parser = ISAParser() + def isControlReg(self): + return 1 -##################################################################### -# -# Support Classes -# -##################################################################### + def makeConstructor(self): + c = '' + if self.is_src: + c += '\n\t_srcRegIdx[%d] = %s + Ctrl_Base_DepTag;' % \ + (self.src_reg_idx, self.reg_spec) + if self.is_dest: + c += '\n\t_destRegIdx[%d] = %s + Ctrl_Base_DepTag;' % \ + (self.dest_reg_idx, self.reg_spec) + return c -# Expand template with CPU-specific references into a dictionary with -# an entry for each CPU model name. The entry key is the model name -# and the corresponding value is the template with the CPU-specific -# refs substituted for that model. -def expand_cpu_symbols_to_dict(template): - # Protect '%'s that don't go with CPU-specific terms - t = re.sub(r'%(?!\(CPU_)', '%%', template) - result = {} - for cpu in cpu_models: - result[cpu.name] = t % cpu.strings - return result + def makeRead(self): + bit_select = 0 + if (self.ctype == 'float' or self.ctype == 'double'): + error(0, 'Attempt to read control register as FP') + if self.read_code != None: + return self.buildReadCode('readMiscRegOperand') + base = 'xc->readMiscRegOperand(this, %s)' % self.src_reg_idx + if self.size == self.dflt_size: + return '%s = %s;\n' % (self.base_name, base) + else: + return '%s = bits(%s, %d, 0);\n' % \ + (self.base_name, base, self.size-1) -# *If* the template has CPU-specific references, return a single -# string containing a copy of the template for each CPU model with the -# corresponding values substituted in. If the template has no -# CPU-specific references, it is returned unmodified. -def expand_cpu_symbols_to_string(template): - if template.find('%(CPU_') != -1: - return reduce(lambda x,y: x+y, - expand_cpu_symbols_to_dict(template).values()) - else: - return template + def makeWrite(self): + if (self.ctype == 'float' or self.ctype == 'double'): + error(0, 'Attempt to write control register as FP') + if self.write_code != None: + return self.buildWriteCode('setMiscRegOperand') + wb = 'xc->setMiscRegOperand(this, %s, %s);\n' % \ + (self.dest_reg_idx, self.base_name) + wb += 'if (traceData) { traceData->setData(%s); }' % \ + self.base_name + return wb + +class MemOperand(Operand): + def isMem(self): + return 1 + + def makeConstructor(self): + return '' + + def makeDecl(self): + # Note that initializations in the declarations are solely + # to avoid 'uninitialized variable' errors from the compiler. + # Declare memory data variable. + if self.ctype in ['Twin32_t','Twin64_t']: + return "%s %s; %s.a = 0; %s.b = 0;\n" % \ + (self.ctype, self.base_name, self.base_name, self.base_name) + return '%s %s = 0;\n' % (self.ctype, self.base_name) + + def makeRead(self): + if self.read_code != None: + return self.buildReadCode() + return '' + + def makeWrite(self): + if self.write_code != None: + return self.buildWriteCode() + return '' + + # Return the memory access size *in bits*, suitable for + # forming a type via "uint%d_t". Divide by 8 if you want bytes. + def makeAccSize(self): + return self.size + +class PCOperand(Operand): + def makeConstructor(self): + return '' + + def makeRead(self): + return '%s = xc->readPC();\n' % self.base_name + + def makeWrite(self): + return 'xc->setPC(%s);\n' % self.base_name + +class UPCOperand(Operand): + def makeConstructor(self): + return '' + + def makeRead(self): + if self.read_code != None: + return self.buildReadCode('readMicroPC') + return '%s = xc->readMicroPC();\n' % self.base_name + + def makeWrite(self): + if self.write_code != None: + return self.buildWriteCode('setMicroPC') + return 'xc->setMicroPC(%s);\n' % self.base_name + +class NUPCOperand(Operand): + def makeConstructor(self): + return '' + + def makeRead(self): + if self.read_code != None: + return self.buildReadCode('readNextMicroPC') + return '%s = xc->readNextMicroPC();\n' % self.base_name + + def makeWrite(self): + if self.write_code != None: + return self.buildWriteCode('setNextMicroPC') + return 'xc->setNextMicroPC(%s);\n' % self.base_name + +class NPCOperand(Operand): + def makeConstructor(self): + return '' + + def makeRead(self): + if self.read_code != None: + return self.buildReadCode('readNextPC') + return '%s = xc->readNextPC();\n' % self.base_name + + def makeWrite(self): + if self.write_code != None: + return self.buildWriteCode('setNextPC') + return 'xc->setNextPC(%s);\n' % self.base_name + +class NNPCOperand(Operand): + def makeConstructor(self): + return '' + + def makeRead(self): + if self.read_code != None: + return self.buildReadCode('readNextNPC') + return '%s = xc->readNextNPC();\n' % self.base_name + + def makeWrite(self): + if self.write_code != None: + return self.buildWriteCode('setNextNPC') + return 'xc->setNextNPC(%s);\n' % self.base_name + +def buildOperandNameMap(user_dict, lineno): + global operandNameMap + operandNameMap = {} + for (op_name, val) in user_dict.iteritems(): + (base_cls_name, dflt_ext, reg_spec, flags, sort_pri) = val[:5] + if len(val) > 5: + read_code = val[5] + else: + read_code = None + if len(val) > 6: + write_code = val[6] + else: + write_code = None + if len(val) > 7: + error(lineno, + 'error: too many attributes for operand "%s"' % + base_cls_name) + + (dflt_size, dflt_ctype, dflt_is_signed) = operandTypeMap[dflt_ext] + # Canonical flag structure is a triple of lists, where each list + # indicates the set of flags implied by this operand always, when + # used as a source, and when used as a dest, respectively. + # For simplicity this can be initialized using a variety of fairly + # obvious shortcuts; we convert these to canonical form here. + if not flags: + # no flags specified (e.g., 'None') + flags = ( [], [], [] ) + elif isinstance(flags, str): + # a single flag: assumed to be unconditional + flags = ( [ flags ], [], [] ) + elif isinstance(flags, list): + # a list of flags: also assumed to be unconditional + flags = ( flags, [], [] ) + elif isinstance(flags, tuple): + # it's a tuple: it should be a triple, + # but each item could be a single string or a list + (uncond_flags, src_flags, dest_flags) = flags + flags = (makeList(uncond_flags), + makeList(src_flags), makeList(dest_flags)) + # Accumulate attributes of new operand class in tmp_dict + tmp_dict = {} + for attr in ('dflt_ext', 'reg_spec', 'flags', 'sort_pri', + 'dflt_size', 'dflt_ctype', 'dflt_is_signed', + 'read_code', 'write_code'): + tmp_dict[attr] = eval(attr) + tmp_dict['base_name'] = op_name + # New class name will be e.g. "IntReg_Ra" + cls_name = base_cls_name + '_' + op_name + # Evaluate string arg to get class object. Note that the + # actual base class for "IntReg" is "IntRegOperand", i.e. we + # have to append "Operand". + try: + base_cls = eval(base_cls_name + 'Operand') + except NameError: + error(lineno, + 'error: unknown operand base class "%s"' % base_cls_name) + # The following statement creates a new class called + # as a subclass of with the attributes + # in tmp_dict, just as if we evaluated a class declaration. + operandNameMap[op_name] = type(cls_name, (base_cls,), tmp_dict) + + # Define operand variables. + operands = user_dict.keys() + + operandsREString = (r''' + (? 1: - Name += name[1:] - context.update({ 'name': name, 'Name': Name }) - try: - vars = self.func(self.user_code, context, *args[0], **args[1]) - except Exception, exc: - error(lineno, 'error defining "%s": %s.' % (name, exc)) - for k in vars.keys(): - if k not in ('header_output', 'decoder_output', - 'exec_output', 'decode_block'): - del vars[k] - return GenCode(**vars) +class InstObjParams(object): + def __init__(self, mnem, class_name, base_class = '', + snippets = {}, opt_args = []): + self.mnemonic = mnem + self.class_name = class_name + self.base_class = base_class + if not isinstance(snippets, dict): + snippets = {'code' : snippets} + compositeCode = ' '.join(map(str, snippets.values())) + self.snippets = snippets -# Special null format to catch an implicit-format instruction -# definition outside of any format block. -class NoFormat(object): - def __init__(self): - self.defaultInst = '' + self.operands = OperandList(compositeCode) + self.constructor = self.operands.concatAttrStrings('constructor') + self.constructor += \ + '\n\t_numSrcRegs = %d;' % self.operands.numSrcRegs + self.constructor += \ + '\n\t_numDestRegs = %d;' % self.operands.numDestRegs + self.constructor += \ + '\n\t_numFPDestRegs = %d;' % self.operands.numFPDestRegs + self.constructor += \ + '\n\t_numIntDestRegs = %d;' % self.operands.numIntDestRegs + self.flags = self.operands.concatAttrLists('flags') - def defineInst(self, name, args, lineno): - error(lineno, - 'instruction definition "%s" with no active format!' % name) + # Make a basic guess on the operand class (function unit type). + # These are good enough for most cases, and can be overridden + # later otherwise. + if 'IsStore' in self.flags: + self.op_class = 'MemWriteOp' + elif 'IsLoad' in self.flags or 'IsPrefetch' in self.flags: + self.op_class = 'MemReadOp' + elif 'IsFloating' in self.flags: + self.op_class = 'FloatAddOp' + else: + self.op_class = 'IntAluOp' -# This dictionary maps format name strings to Format objects. -formatMap = {} + # Optional arguments are assumed to be either StaticInst flags + # or an OpClass value. To avoid having to import a complete + # list of these values to match against, we do it ad-hoc + # with regexps. + for oa in opt_args: + if instFlagRE.match(oa): + self.flags.append(oa) + elif opClassRE.match(oa): + self.op_class = oa + else: + error(0, 'InstObjParams: optional arg "%s" not recognized ' + 'as StaticInst::Flag or OpClass.' % oa) -# Define a new format -def defFormat(id, params, code, lineno): - # make sure we haven't already defined this one - if formatMap.get(id, None) != None: - error(lineno, 'format %s redefined.' % id) - # create new object and store in global map - formatMap[id] = Format(id, params, code) + # add flag initialization to contructor here to include + # any flags added via opt_args + self.constructor += makeFlagConstructor(self.flags) + # if 'IsFloating' is set, add call to the FP enable check + # function (which should be provided by isa_desc via a declare) + if 'IsFloating' in self.flags: + self.fp_enable_check = 'fault = checkFpEnableFault(xc);' + else: + self.fp_enable_check = '' ############## # Stack: a simple stack object. Used for both formats (formatStack) @@ -929,7 +1171,7 @@ class Stack(list): formatStack = Stack(NoFormat()) # The global default case stack. -defaultStack = Stack( None ) +defaultStack = Stack(None) # Global stack that tracks current file and line number. # Each element is a tuple (filename, lineno) that records the @@ -937,988 +1179,744 @@ defaultStack = Stack( None ) # it was included. fileNameStack = Stack() -################### -# Utility functions - -# -# Indent every line in string 's' by two spaces -# (except preprocessor directives). -# Used to make nested code blocks look pretty. -# -def indent(s): - return re.sub(r'(?m)^(?!#)', ' ', s) - -# -# Munge a somewhat arbitrarily formatted piece of Python code -# (e.g. from a format 'let' block) into something whose indentation -# will get by the Python parser. -# -# The two keys here are that Python will give a syntax error if -# there's any whitespace at the beginning of the first line, and that -# all lines at the same lexical nesting level must have identical -# indentation. Unfortunately the way code literals work, an entire -# let block tends to have some initial indentation. Rather than -# trying to figure out what that is and strip it off, we prepend 'if -# 1:' to make the let code the nested block inside the if (and have -# the parser automatically deal with the indentation for us). -# -# We don't want to do this if (1) the code block is empty or (2) the -# first line of the block doesn't have any whitespace at the front. - -def fixPythonIndentation(s): - # get rid of blank lines first - s = re.sub(r'(?m)^\s*\n', '', s); - if (s != '' and re.match(r'[ \t]', s[0])): - s = 'if 1:\n' + s - return s - -# Error handler. Just call exit. Output formatted to work under -# Emacs compile-mode. Optional 'print_traceback' arg, if set to True, -# prints a Python stack backtrace too (can be handy when trying to -# debug the parser itself). -def error(lineno, string, print_traceback = False): - spaces = "" - for (filename, line) in fileNameStack[0:-1]: - print spaces + "In file included from " + filename + ":" - spaces += " " - # Print a Python stack backtrace if requested. - if (print_traceback): - traceback.print_exc() - if lineno != 0: - line_str = "%d:" % lineno - else: - line_str = "" - sys.exit(spaces + "%s:%s %s" % (fileNameStack[-1][0], line_str, string)) - - -##################################################################### -# -# Bitfield Operator Support -# -##################################################################### - -bitOp1ArgRE = re.compile(r'<\s*(\w+)\s*:\s*>') - -bitOpWordRE = re.compile(r'(?') -bitOpExprRE = re.compile(r'\)<\s*(\w+)\s*:\s*(\w+)\s*>') - -def substBitOps(code): - # first convert single-bit selectors to two-index form - # i.e., --> - code = bitOp1ArgRE.sub(r'<\1:\1>', code) - # simple case: selector applied to ID (name) - # i.e., foo --> bits(foo, a, b) - code = bitOpWordRE.sub(r'bits(\1, \2, \3)', code) - # if selector is applied to expression (ending in ')'), - # we need to search backward for matching '(' - match = bitOpExprRE.search(code) - while match: - exprEnd = match.start() - here = exprEnd - 1 - nestLevel = 1 - while nestLevel > 0: - if code[here] == '(': - nestLevel -= 1 - elif code[here] == ')': - nestLevel += 1 - here -= 1 - if here < 0: - sys.exit("Didn't find '('!") - exprStart = here+1 - newExpr = r'bits(%s, %s, %s)' % (code[exprStart:exprEnd+1], - match.group(1), match.group(2)) - code = code[:exprStart] + newExpr + code[match.end():] - match = bitOpExprRE.search(code) - return code - - -#################### -# Template objects. -# -# Template objects are format strings that allow substitution from -# the attribute spaces of other objects (e.g. InstObjParams instances). - -labelRE = re.compile(r'(?(%s)' % (self.size, self.base_name) - else: - final_val = self.base_name - code = self.write_code % {"name": self.base_name, - "func": func, - "op_idx": self.dest_reg_idx, - "reg_idx": self.reg_spec, - "size": self.size, - "ctype": self.ctype, - "final_val": final_val} - return ''' - { - %s final_val = %s; - %s; - if (traceData) { traceData->setData(final_val); } - }''' % (self.dflt_ctype, final_val, code) +namespace %(namespace)s { - def __init__(self, full_name, ext, is_src, is_dest): - self.full_name = full_name - self.ext = ext - self.is_src = is_src - self.is_dest = is_dest - # The 'effective extension' (eff_ext) is either the actual - # extension, if one was explicitly provided, or the default. - if ext: - self.eff_ext = ext - else: - self.eff_ext = self.dflt_ext +%(namespace_output)s - (self.size, self.ctype, self.is_signed) = operandTypeMap[self.eff_ext] +} // namespace %(namespace)s - # note that mem_acc_size is undefined for non-mem operands... - # template must be careful not to use it if it doesn't apply. - if self.isMem(): - self.mem_acc_size = self.makeAccSize() - if self.ctype in ['Twin32_t', 'Twin64_t']: - self.mem_acc_type = 'Twin' - else: - self.mem_acc_type = 'uint' +%(decode_function)s +''' - # Finalize additional fields (primarily code fields). This step - # is done separately since some of these fields may depend on the - # register index enumeration that hasn't been performed yet at the - # time of __init__(). - def finalize(self): - self.flags = self.getFlags() - self.constructor = self.makeConstructor() - self.op_decl = self.makeDecl() +max_inst_regs_template = ''' +/* + * DO NOT EDIT THIS FILE!!! + * + * It was automatically generated from the ISA description in %(filename)s + */ - if self.is_src: - self.op_rd = self.makeRead() - self.op_src_decl = self.makeDecl() - else: - self.op_rd = '' - self.op_src_decl = '' +namespace %(namespace)s { - if self.is_dest: - self.op_wb = self.makeWrite() - self.op_dest_decl = self.makeDecl() - else: - self.op_wb = '' - self.op_dest_decl = '' + const int MaxInstSrcRegs = %(MaxInstSrcRegs)d; + const int MaxInstDestRegs = %(MaxInstDestRegs)d; - def isMem(self): - return 0 +} // namespace %(namespace)s - def isReg(self): - return 0 +''' - def isFloatReg(self): - return 0 +class ISAParser(Grammar): + def __init__(self, *args, **kwargs): + super(ISAParser, self).__init__(*args, **kwargs) + self.templateMap = {} - def isIntReg(self): - return 0 + ##################################################################### + # + # Lexer + # + # The PLY lexer module takes two things as input: + # - A list of token names (the string list 'tokens') + # - A regular expression describing a match for each token. The + # regexp for token FOO can be provided in two ways: + # - as a string variable named t_FOO + # - as the doc string for a function named t_FOO. In this case, + # the function is also executed, allowing an action to be + # associated with each token match. + # + ##################################################################### - def isControlReg(self): - return 0 + # Reserved words. These are listed separately as they are matched + # using the same regexp as generic IDs, but distinguished in the + # t_ID() function. The PLY documentation suggests this approach. + reserved = ( + 'BITFIELD', 'DECODE', 'DECODER', 'DEFAULT', 'DEF', 'EXEC', 'FORMAT', + 'HEADER', 'LET', 'NAMESPACE', 'OPERAND_TYPES', 'OPERANDS', + 'OUTPUT', 'SIGNED', 'TEMPLATE' + ) - def getFlags(self): - # note the empty slice '[:]' gives us a copy of self.flags[0] - # instead of a reference to it - my_flags = self.flags[0][:] - if self.is_src: - my_flags += self.flags[1] - if self.is_dest: - my_flags += self.flags[2] - return my_flags + # List of tokens. The lex module requires this. + tokens = reserved + ( + # identifier + 'ID', - def makeDecl(self): - # Note that initializations in the declarations are solely - # to avoid 'uninitialized variable' errors from the compiler. - return self.ctype + ' ' + self.base_name + ' = 0;\n'; + # integer literal + 'INTLIT', -class IntRegOperand(Operand): - def isReg(self): - return 1 + # string literal + 'STRLIT', - def isIntReg(self): - return 1 + # code literal + 'CODELIT', - def makeConstructor(self): - c = '' - if self.is_src: - c += '\n\t_srcRegIdx[%d] = %s;' % \ - (self.src_reg_idx, self.reg_spec) - if self.is_dest: - c += '\n\t_destRegIdx[%d] = %s;' % \ - (self.dest_reg_idx, self.reg_spec) - return c + # ( ) [ ] { } < > , ; . : :: * + 'LPAREN', 'RPAREN', + 'LBRACKET', 'RBRACKET', + 'LBRACE', 'RBRACE', + 'LESS', 'GREATER', 'EQUALS', + 'COMMA', 'SEMI', 'DOT', 'COLON', 'DBLCOLON', + 'ASTERISK', - def makeRead(self): - if (self.ctype == 'float' or self.ctype == 'double'): - error(0, 'Attempt to read integer register as FP') - if self.read_code != None: - return self.buildReadCode('readIntRegOperand') - if (self.size == self.dflt_size): - return '%s = xc->readIntRegOperand(this, %d);\n' % \ - (self.base_name, self.src_reg_idx) - elif (self.size > self.dflt_size): - int_reg_val = 'xc->readIntRegOperand(this, %d)' % \ - (self.src_reg_idx) - if (self.is_signed): - int_reg_val = 'sext<%d>(%s)' % (self.dflt_size, int_reg_val) - return '%s = %s;\n' % (self.base_name, int_reg_val) - else: - return '%s = bits(xc->readIntRegOperand(this, %d), %d, 0);\n' % \ - (self.base_name, self.src_reg_idx, self.size-1) + # C preprocessor directives + 'CPPDIRECTIVE' + + # The following are matched but never returned. commented out to + # suppress PLY warning + # newfile directive + # 'NEWFILE', + + # endfile directive + # 'ENDFILE' + ) + + # Regular expressions for token matching + t_LPAREN = r'\(' + t_RPAREN = r'\)' + t_LBRACKET = r'\[' + t_RBRACKET = r'\]' + t_LBRACE = r'\{' + t_RBRACE = r'\}' + t_LESS = r'\<' + t_GREATER = r'\>' + t_EQUALS = r'=' + t_COMMA = r',' + t_SEMI = r';' + t_DOT = r'\.' + t_COLON = r':' + t_DBLCOLON = r'::' + t_ASTERISK = r'\*' + + # Identifiers and reserved words + reserved_map = { } + for r in reserved: + reserved_map[r.lower()] = r + + def t_ID(self, t): + r'[A-Za-z_]\w*' + t.type = self.reserved_map.get(t.value, 'ID') + return t + + # Integer literal + def t_INTLIT(self, t): + r'-?(0x[\da-fA-F]+)|\d+' + try: + t.value = int(t.value,0) + except ValueError: + error(t.lexer.lineno, 'Integer value "%s" too large' % t.value) + t.value = 0 + return t + + # String literal. Note that these use only single quotes, and + # can span multiple lines. + def t_STRLIT(self, t): + r"(?m)'([^'])+'" + # strip off quotes + t.value = t.value[1:-1] + t.lexer.lineno += t.value.count('\n') + return t + + + # "Code literal"... like a string literal, but delimiters are + # '{{' and '}}' so they get formatted nicely under emacs c-mode + def t_CODELIT(self, t): + r"(?m)\{\{([^\}]|}(?!\}))+\}\}" + # strip off {{ & }} + t.value = t.value[2:-2] + t.lexer.lineno += t.value.count('\n') + return t - def makeWrite(self): - if (self.ctype == 'float' or self.ctype == 'double'): - error(0, 'Attempt to write integer register as FP') - if self.write_code != None: - return self.buildWriteCode('setIntRegOperand') - if (self.size != self.dflt_size and self.is_signed): - final_val = 'sext<%d>(%s)' % (self.size, self.base_name) - else: - final_val = self.base_name - wb = ''' - { - %s final_val = %s; - xc->setIntRegOperand(this, %d, final_val);\n - if (traceData) { traceData->setData(final_val); } - }''' % (self.dflt_ctype, final_val, self.dest_reg_idx) - return wb + def t_CPPDIRECTIVE(self, t): + r'^\#[^\#].*\n' + t.lexer.lineno += t.value.count('\n') + return t -class FloatRegOperand(Operand): - def isReg(self): - return 1 + def t_NEWFILE(self, t): + r'^\#\#newfile\s+"[\w/.-]*"' + fileNameStack.push((t.value[11:-1], t.lexer.lineno)) + t.lexer.lineno = 0 - def isFloatReg(self): - return 1 + def t_ENDFILE(self, t): + r'^\#\#endfile' + (old_filename, t.lexer.lineno) = fileNameStack.pop() - def makeConstructor(self): - c = '' - if self.is_src: - c += '\n\t_srcRegIdx[%d] = %s + FP_Base_DepTag;' % \ - (self.src_reg_idx, self.reg_spec) - if self.is_dest: - c += '\n\t_destRegIdx[%d] = %s + FP_Base_DepTag;' % \ - (self.dest_reg_idx, self.reg_spec) - return c + # + # The functions t_NEWLINE, t_ignore, and t_error are + # special for the lex module. + # - def makeRead(self): - bit_select = 0 - if (self.ctype == 'float' or self.ctype == 'double'): - func = 'readFloatRegOperand' - else: - func = 'readFloatRegOperandBits' - if (self.size != self.dflt_size): - bit_select = 1 - base = 'xc->%s(this, %d)' % (func, self.src_reg_idx) - if self.read_code != None: - return self.buildReadCode(func) - if bit_select: - return '%s = bits(%s, %d, 0);\n' % \ - (self.base_name, base, self.size-1) - else: - return '%s = %s;\n' % (self.base_name, base) + # Newlines + def t_NEWLINE(self, t): + r'\n+' + t.lexer.lineno += t.value.count('\n') - def makeWrite(self): - final_val = self.base_name - final_ctype = self.ctype - if (self.ctype == 'float' or self.ctype == 'double'): - func = 'setFloatRegOperand' - elif (self.ctype == 'uint32_t' or self.ctype == 'uint64_t'): - func = 'setFloatRegOperandBits' - else: - func = 'setFloatRegOperandBits' - final_ctype = 'uint%d_t' % self.dflt_size - if (self.size != self.dflt_size and self.is_signed): - final_val = 'sext<%d>(%s)' % (self.size, self.base_name) - if self.write_code != None: - return self.buildWriteCode(func) - wb = ''' - { - %s final_val = %s; - xc->%s(this, %d, final_val);\n - if (traceData) { traceData->setData(final_val); } - }''' % (final_ctype, final_val, func, self.dest_reg_idx) - return wb + # Comments + def t_comment(self, t): + r'//.*' -class ControlRegOperand(Operand): - def isReg(self): - return 1 + # Completely ignored characters + t_ignore = ' \t\x0c' - def isControlReg(self): - return 1 + # Error handler + def t_error(self, t): + error(t.lexer.lineno, "illegal character '%s'" % t.value[0]) + t.skip(1) - def makeConstructor(self): - c = '' - if self.is_src: - c += '\n\t_srcRegIdx[%d] = %s + Ctrl_Base_DepTag;' % \ - (self.src_reg_idx, self.reg_spec) - if self.is_dest: - c += '\n\t_destRegIdx[%d] = %s + Ctrl_Base_DepTag;' % \ - (self.dest_reg_idx, self.reg_spec) - return c + ##################################################################### + # + # Parser + # + # Every function whose name starts with 'p_' defines a grammar + # rule. The rule is encoded in the function's doc string, while + # the function body provides the action taken when the rule is + # matched. The argument to each function is a list of the values + # of the rule's symbols: t[0] for the LHS, and t[1..n] for the + # symbols on the RHS. For tokens, the value is copied from the + # t.value attribute provided by the lexer. For non-terminals, the + # value is assigned by the producing rule; i.e., the job of the + # grammar rule function is to set the value for the non-terminal + # on the LHS (by assigning to t[0]). + ##################################################################### - def makeRead(self): - bit_select = 0 - if (self.ctype == 'float' or self.ctype == 'double'): - error(0, 'Attempt to read control register as FP') - if self.read_code != None: - return self.buildReadCode('readMiscRegOperand') - base = 'xc->readMiscRegOperand(this, %s)' % self.src_reg_idx - if self.size == self.dflt_size: - return '%s = %s;\n' % (self.base_name, base) - else: - return '%s = bits(%s, %d, 0);\n' % \ - (self.base_name, base, self.size-1) + # The LHS of the first grammar rule is used as the start symbol + # (in this case, 'specification'). Note that this rule enforces + # that there will be exactly one namespace declaration, with 0 or + # more global defs/decls before and after it. The defs & decls + # before the namespace decl will be outside the namespace; those + # after will be inside. The decoder function is always inside the + # namespace. + def p_specification(self, t): + 'specification : opt_defs_and_outputs name_decl opt_defs_and_outputs decode_block' + global_code = t[1] + isa_name = t[2] + namespace = isa_name + "Inst" + # wrap the decode block as a function definition + t[4].wrap_decode_block(''' +StaticInstPtr +%(isa_name)s::decodeInst(%(isa_name)s::ExtMachInst machInst) +{ + using namespace %(namespace)s; +''' % vars(), '}') + # both the latter output blocks and the decode block are in + # the namespace + namespace_code = t[3] + t[4] + # pass it all back to the caller of yacc.parse() + t[0] = (isa_name, namespace, global_code, namespace_code) - def makeWrite(self): - if (self.ctype == 'float' or self.ctype == 'double'): - error(0, 'Attempt to write control register as FP') - if self.write_code != None: - return self.buildWriteCode('setMiscRegOperand') - wb = 'xc->setMiscRegOperand(this, %s, %s);\n' % \ - (self.dest_reg_idx, self.base_name) - wb += 'if (traceData) { traceData->setData(%s); }' % \ - self.base_name - return wb + # ISA name declaration looks like "namespace ;" + def p_name_decl(self, t): + 'name_decl : NAMESPACE ID SEMI' + t[0] = t[2] -class MemOperand(Operand): - def isMem(self): - return 1 + # 'opt_defs_and_outputs' is a possibly empty sequence of + # def and/or output statements. + def p_opt_defs_and_outputs_0(self, t): + 'opt_defs_and_outputs : empty' + t[0] = GenCode() - def makeConstructor(self): - return '' + def p_opt_defs_and_outputs_1(self, t): + 'opt_defs_and_outputs : defs_and_outputs' + t[0] = t[1] - def makeDecl(self): - # Note that initializations in the declarations are solely - # to avoid 'uninitialized variable' errors from the compiler. - # Declare memory data variable. - if self.ctype in ['Twin32_t','Twin64_t']: - return "%s %s; %s.a = 0; %s.b = 0;\n" % \ - (self.ctype, self.base_name, self.base_name, self.base_name) - return '%s %s = 0;\n' % (self.ctype, self.base_name) + def p_defs_and_outputs_0(self, t): + 'defs_and_outputs : def_or_output' + t[0] = t[1] - def makeRead(self): - if self.read_code != None: - return self.buildReadCode() - return '' + def p_defs_and_outputs_1(self, t): + 'defs_and_outputs : defs_and_outputs def_or_output' + t[0] = t[1] + t[2] - def makeWrite(self): - if self.write_code != None: - return self.buildWriteCode() - return '' + # The list of possible definition/output statements. + def p_def_or_output(self, t): + '''def_or_output : def_format + | def_bitfield + | def_bitfield_struct + | def_template + | def_operand_types + | def_operands + | output_header + | output_decoder + | output_exec + | global_let''' + t[0] = t[1] - # Return the memory access size *in bits*, suitable for - # forming a type via "uint%d_t". Divide by 8 if you want bytes. - def makeAccSize(self): - return self.size + # Output blocks 'output {{...}}' (C++ code blocks) are copied + # directly to the appropriate output section. -class PCOperand(Operand): - def makeConstructor(self): - return '' + # Massage output block by substituting in template definitions and + # bit operators. We handle '%'s embedded in the string that don't + # indicate template substitutions (or CPU-specific symbols, which + # get handled in GenCode) by doubling them first so that the + # format operation will reduce them back to single '%'s. + def process_output(self, s): + s = protect_non_subst_percents(s) + # protects cpu-specific symbols too + s = protect_cpu_symbols(s) + return substBitOps(s % self.templateMap) - def makeRead(self): - return '%s = xc->readPC();\n' % self.base_name + def p_output_header(self, t): + 'output_header : OUTPUT HEADER CODELIT SEMI' + t[0] = GenCode(header_output = self.process_output(t[3])) + + def p_output_decoder(self, t): + 'output_decoder : OUTPUT DECODER CODELIT SEMI' + t[0] = GenCode(decoder_output = self.process_output(t[3])) + + def p_output_exec(self, t): + 'output_exec : OUTPUT EXEC CODELIT SEMI' + t[0] = GenCode(exec_output = self.process_output(t[3])) - def makeWrite(self): - return 'xc->setPC(%s);\n' % self.base_name + # global let blocks 'let {{...}}' (Python code blocks) are + # executed directly when seen. Note that these execute in a + # special variable context 'exportContext' to prevent the code + # from polluting this script's namespace. + def p_global_let(self, t): + 'global_let : LET CODELIT SEMI' + updateExportContext() + exportContext["header_output"] = '' + exportContext["decoder_output"] = '' + exportContext["exec_output"] = '' + exportContext["decode_block"] = '' + try: + exec fixPythonIndentation(t[2]) in exportContext + except Exception, exc: + error(t.lexer.lineno, + 'error: %s in global let block "%s".' % (exc, t[2])) + t[0] = GenCode(header_output = exportContext["header_output"], + decoder_output = exportContext["decoder_output"], + exec_output = exportContext["exec_output"], + decode_block = exportContext["decode_block"]) -class UPCOperand(Operand): - def makeConstructor(self): - return '' + # Define the mapping from operand type extensions to C++ types and + # bit widths (stored in operandTypeMap). + def p_def_operand_types(self, t): + 'def_operand_types : DEF OPERAND_TYPES CODELIT SEMI' + try: + user_dict = eval('{' + t[3] + '}') + except Exception, exc: + error(t.lexer.lineno, + 'error: %s in def operand_types block "%s".' % (exc, t[3])) + buildOperandTypeMap(user_dict, t.lexer.lineno) + t[0] = GenCode() # contributes nothing to the output C++ file - def makeRead(self): - if self.read_code != None: - return self.buildReadCode('readMicroPC') - return '%s = xc->readMicroPC();\n' % self.base_name + # Define the mapping from operand names to operand classes and + # other traits. Stored in operandNameMap. + def p_def_operands(self, t): + 'def_operands : DEF OPERANDS CODELIT SEMI' + if not globals().has_key('operandTypeMap'): + error(t.lexer.lineno, + 'error: operand types must be defined before operands') + try: + user_dict = eval('{' + t[3] + '}', exportContext) + except Exception, exc: + error(t.lexer.lineno, + 'error: %s in def operands block "%s".' % (exc, t[3])) + buildOperandNameMap(user_dict, t.lexer.lineno) + t[0] = GenCode() # contributes nothing to the output C++ file - def makeWrite(self): - if self.write_code != None: - return self.buildWriteCode('setMicroPC') - return 'xc->setMicroPC(%s);\n' % self.base_name + # A bitfield definition looks like: + # 'def [signed] bitfield [:]' + # This generates a preprocessor macro in the output file. + def p_def_bitfield_0(self, t): + 'def_bitfield : DEF opt_signed BITFIELD ID LESS INTLIT COLON INTLIT GREATER SEMI' + expr = 'bits(machInst, %2d, %2d)' % (t[6], t[8]) + if (t[2] == 'signed'): + expr = 'sext<%d>(%s)' % (t[6] - t[8] + 1, expr) + hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr) + t[0] = GenCode(header_output = hash_define) -class NUPCOperand(Operand): - def makeConstructor(self): - return '' + # alternate form for single bit: 'def [signed] bitfield []' + def p_def_bitfield_1(self, t): + 'def_bitfield : DEF opt_signed BITFIELD ID LESS INTLIT GREATER SEMI' + expr = 'bits(machInst, %2d, %2d)' % (t[6], t[6]) + if (t[2] == 'signed'): + expr = 'sext<%d>(%s)' % (1, expr) + hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr) + t[0] = GenCode(header_output = hash_define) - def makeRead(self): - if self.read_code != None: - return self.buildReadCode('readNextMicroPC') - return '%s = xc->readNextMicroPC();\n' % self.base_name + # alternate form for structure member: 'def bitfield ' + def p_def_bitfield_struct(self, t): + 'def_bitfield_struct : DEF opt_signed BITFIELD ID id_with_dot SEMI' + if (t[2] != ''): + error(t.lexer.lineno, + 'error: structure bitfields are always unsigned.') + expr = 'machInst.%s' % t[5] + hash_define = '#undef %s\n#define %s\t%s\n' % (t[4], t[4], expr) + t[0] = GenCode(header_output = hash_define) - def makeWrite(self): - if self.write_code != None: - return self.buildWriteCode('setNextMicroPC') - return 'xc->setNextMicroPC(%s);\n' % self.base_name + def p_id_with_dot_0(self, t): + 'id_with_dot : ID' + t[0] = t[1] -class NPCOperand(Operand): - def makeConstructor(self): - return '' + def p_id_with_dot_1(self, t): + 'id_with_dot : ID DOT id_with_dot' + t[0] = t[1] + t[2] + t[3] - def makeRead(self): - if self.read_code != None: - return self.buildReadCode('readNextPC') - return '%s = xc->readNextPC();\n' % self.base_name + def p_opt_signed_0(self, t): + 'opt_signed : SIGNED' + t[0] = t[1] - def makeWrite(self): - if self.write_code != None: - return self.buildWriteCode('setNextPC') - return 'xc->setNextPC(%s);\n' % self.base_name + def p_opt_signed_1(self, t): + 'opt_signed : empty' + t[0] = '' -class NNPCOperand(Operand): - def makeConstructor(self): - return '' + def p_def_template(self, t): + 'def_template : DEF TEMPLATE ID CODELIT SEMI' + self.templateMap[t[3]] = Template(t[4]) + t[0] = GenCode() - def makeRead(self): - if self.read_code != None: - return self.buildReadCode('readNextNPC') - return '%s = xc->readNextNPC();\n' % self.base_name + # An instruction format definition looks like + # "def format () {{...}};" + def p_def_format(self, t): + 'def_format : DEF FORMAT ID LPAREN param_list RPAREN CODELIT SEMI' + (id, params, code) = (t[3], t[5], t[7]) + defFormat(id, params, code, t.lexer.lineno) + t[0] = GenCode() - def makeWrite(self): - if self.write_code != None: - return self.buildWriteCode('setNextNPC') - return 'xc->setNextNPC(%s);\n' % self.base_name + # The formal parameter list for an instruction format is a + # possibly empty list of comma-separated parameters. Positional + # (standard, non-keyword) parameters must come first, followed by + # keyword parameters, followed by a '*foo' parameter that gets + # excess positional arguments (as in Python). Each of these three + # parameter categories is optional. + # + # Note that we do not support the '**foo' parameter for collecting + # otherwise undefined keyword args. Otherwise the parameter list + # is (I believe) identical to what is supported in Python. + # + # The param list generates a tuple, where the first element is a + # list of the positional params and the second element is a dict + # containing the keyword params. + def p_param_list_0(self, t): + 'param_list : positional_param_list COMMA nonpositional_param_list' + t[0] = t[1] + t[3] -def buildOperandNameMap(user_dict, lineno): - global operandNameMap - operandNameMap = {} - for (op_name, val) in user_dict.iteritems(): - (base_cls_name, dflt_ext, reg_spec, flags, sort_pri) = val[:5] - if len(val) > 5: - read_code = val[5] - else: - read_code = None - if len(val) > 6: - write_code = val[6] - else: - write_code = None - if len(val) > 7: - error(lineno, - 'error: too many attributes for operand "%s"' % - base_cls_name) - - (dflt_size, dflt_ctype, dflt_is_signed) = operandTypeMap[dflt_ext] - # Canonical flag structure is a triple of lists, where each list - # indicates the set of flags implied by this operand always, when - # used as a source, and when used as a dest, respectively. - # For simplicity this can be initialized using a variety of fairly - # obvious shortcuts; we convert these to canonical form here. - if not flags: - # no flags specified (e.g., 'None') - flags = ( [], [], [] ) - elif isinstance(flags, str): - # a single flag: assumed to be unconditional - flags = ( [ flags ], [], [] ) - elif isinstance(flags, list): - # a list of flags: also assumed to be unconditional - flags = ( flags, [], [] ) - elif isinstance(flags, tuple): - # it's a tuple: it should be a triple, - # but each item could be a single string or a list - (uncond_flags, src_flags, dest_flags) = flags - flags = (makeList(uncond_flags), - makeList(src_flags), makeList(dest_flags)) - # Accumulate attributes of new operand class in tmp_dict - tmp_dict = {} - for attr in ('dflt_ext', 'reg_spec', 'flags', 'sort_pri', - 'dflt_size', 'dflt_ctype', 'dflt_is_signed', - 'read_code', 'write_code'): - tmp_dict[attr] = eval(attr) - tmp_dict['base_name'] = op_name - # New class name will be e.g. "IntReg_Ra" - cls_name = base_cls_name + '_' + op_name - # Evaluate string arg to get class object. Note that the - # actual base class for "IntReg" is "IntRegOperand", i.e. we - # have to append "Operand". - try: - base_cls = eval(base_cls_name + 'Operand') - except NameError: - error(lineno, - 'error: unknown operand base class "%s"' % base_cls_name) - # The following statement creates a new class called - # as a subclass of with the attributes - # in tmp_dict, just as if we evaluated a class declaration. - operandNameMap[op_name] = type(cls_name, (base_cls,), tmp_dict) + def p_param_list_1(self, t): + '''param_list : positional_param_list + | nonpositional_param_list''' + t[0] = t[1] - # Define operand variables. - operands = user_dict.keys() + def p_positional_param_list_0(self, t): + 'positional_param_list : empty' + t[0] = [] - operandsREString = (r''' - (? [, ]* [default ] { ... } + # + def p_decode_block(self, t): + 'decode_block : DECODE ID opt_default LBRACE decode_stmt_list RBRACE' + default_defaults = defaultStack.pop() + codeObj = t[5] + # use the "default defaults" only if there was no explicit + # default statement in decode_stmt_list + if not codeObj.has_decode_default: + codeObj += default_defaults + codeObj.wrap_decode_block('switch (%s) {\n' % t[2], '}\n') + t[0] = codeObj - # internal helper function for concat[Some]Attr{Strings|Lists} - def __internalConcatAttrs(self, attr_name, filter, result): - for op_desc in self.items: - if filter(op_desc): - result += getattr(op_desc, attr_name) - return result + # The opt_default statement serves only to push the "default + # defaults" onto defaultStack. This value will be used by nested + # decode blocks, and used and popped off when the current + # decode_block is processed (in p_decode_block() above). + def p_opt_default_0(self, t): + 'opt_default : empty' + # no default specified: reuse the one currently at the top of + # the stack + defaultStack.push(defaultStack.top()) + # no meaningful value returned + t[0] = None - # return a single string that is the concatenation of the (string) - # values of the specified attribute for all operands - def concatAttrStrings(self, attr_name): - return self.__internalConcatAttrs(attr_name, lambda x: 1, '') + def p_opt_default_1(self, t): + 'opt_default : DEFAULT inst' + # push the new default + codeObj = t[2] + codeObj.wrap_decode_block('\ndefault:\n', 'break;\n') + defaultStack.push(codeObj) + # no meaningful value returned + t[0] = None - # like concatAttrStrings, but only include the values for the operands - # for which the provided filter function returns true - def concatSomeAttrStrings(self, filter, attr_name): - return self.__internalConcatAttrs(attr_name, filter, '') + def p_decode_stmt_list_0(self, t): + 'decode_stmt_list : decode_stmt' + t[0] = t[1] - # return a single list that is the concatenation of the (list) - # values of the specified attribute for all operands - def concatAttrLists(self, attr_name): - return self.__internalConcatAttrs(attr_name, lambda x: 1, []) + def p_decode_stmt_list_1(self, t): + 'decode_stmt_list : decode_stmt decode_stmt_list' + if (t[1].has_decode_default and t[2].has_decode_default): + error(t.lexer.lineno, 'Two default cases in decode block') + t[0] = t[1] + t[2] - # like concatAttrLists, but only include the values for the operands - # for which the provided filter function returns true - def concatSomeAttrLists(self, filter, attr_name): - return self.__internalConcatAttrs(attr_name, filter, []) + # + # Decode statement rules + # + # There are four types of statements allowed in a decode block: + # 1. Format blocks 'format { ... }' + # 2. Nested decode blocks + # 3. Instruction definitions. + # 4. C preprocessor directives. - def sort(self): - self.items.sort(lambda a, b: a.sort_pri - b.sort_pri) -class SubOperandList(OperandList): - '''Find all the operands in the given code block. Returns an operand - descriptor list (instance of class OperandList).''' - def __init__(self, code, master_list): - self.items = [] - self.bases = {} - # delete comments so we don't match on reg specifiers inside - code = commentRE.sub('', code) - # search for operands - next_pos = 0 - while 1: - match = operandsRE.search(code, next_pos) - if not match: - # no more matches: we're done - break - op = match.groups() - # regexp groups are operand full name, base, and extension - (op_full, op_base, op_ext) = op - # find this op in the master list - op_desc = master_list.find_base(op_base) - if not op_desc: - error(0, 'Found operand %s which is not in the master list!' \ - ' This is an internal error' % \ - op_base) - else: - # See if we've already found this operand - op_desc = self.find_base(op_base) - if not op_desc: - # if not, add a reference to it to this sub list - self.append(master_list.bases[op_base]) + # Preprocessor directives found in a decode statement list are + # passed through to the output, replicated to all of the output + # code streams. This works well for ifdefs, so we can ifdef out + # both the declarations and the decode cases generated by an + # instruction definition. Handling them as part of the grammar + # makes it easy to keep them in the right place with respect to + # the code generated by the other statements. + def p_decode_stmt_cpp(self, t): + 'decode_stmt : CPPDIRECTIVE' + t[0] = GenCode(t[1], t[1], t[1], t[1]) - # start next search after end of current match - next_pos = match.end() - self.sort() - self.memOperand = None - for op_desc in self.items: - if op_desc.isMem(): - if self.memOperand: - error(0, "Code block has more than one memory operand.") - self.memOperand = op_desc + # A format block 'format { ... }' sets the default + # instruction format used to handle instruction definitions inside + # the block. This format can be overridden by using an explicit + # format on the instruction definition or with a nested format + # block. + def p_decode_stmt_format(self, t): + 'decode_stmt : FORMAT push_format_id LBRACE decode_stmt_list RBRACE' + # The format will be pushed on the stack when 'push_format_id' + # is processed (see below). Once the parser has recognized + # the full production (though the right brace), we're done + # with the format, so now we can pop it. + formatStack.pop() + t[0] = t[4] -# Regular expression object to match C++ comments -# (used in findOperands()) -commentRE = re.compile(r'//.*\n') + # This rule exists so we can set the current format (& push the + # stack) when we recognize the format name part of the format + # block. + def p_push_format_id(self, t): + 'push_format_id : ID' + try: + formatStack.push(formatMap[t[1]]) + t[0] = ('', '// format %s' % t[1]) + except KeyError: + error(t.lexer.lineno, + 'instruction format "%s" not defined.' % t[1]) -# Regular expression object to match assignment statements -# (used in findOperands()) -assignRE = re.compile(r'\s*=(?!=)', re.MULTILINE) + # Nested decode block: if the value of the current field matches + # the specified constant, do a nested decode on some other field. + def p_decode_stmt_decode(self, t): + 'decode_stmt : case_label COLON decode_block' + label = t[1] + codeObj = t[3] + # just wrap the decoding code from the block as a case in the + # outer switch statement. + codeObj.wrap_decode_block('\n%s:\n' % label) + codeObj.has_decode_default = (label == 'default') + t[0] = codeObj -# Munge operand names in code string to make legal C++ variable names. -# This means getting rid of the type extension if any. -# (Will match base_name attribute of Operand object.) -def substMungedOpNames(code): - return operandsWithExtRE.sub(r'\1', code) + # Instruction definition (finally!). + def p_decode_stmt_inst(self, t): + 'decode_stmt : case_label COLON inst SEMI' + label = t[1] + codeObj = t[3] + codeObj.wrap_decode_block('\n%s:' % label, 'break;\n') + codeObj.has_decode_default = (label == 'default') + t[0] = codeObj -# Fix up code snippets for final substitution in templates. -def mungeSnippet(s): - if isinstance(s, str): - return substMungedOpNames(substBitOps(s)) - else: - return s + # The case label is either a list of one or more constants or + # 'default' + def p_case_label_0(self, t): + 'case_label : intlit_list' + def make_case(intlit): + if intlit >= 2**32: + return 'case ULL(%#x)' % intlit + else: + return 'case %#x' % intlit + t[0] = ': '.join(map(make_case, t[1])) -def makeFlagConstructor(flag_list): - if len(flag_list) == 0: - return '' - # filter out repeated flags - flag_list.sort() - i = 1 - while i < len(flag_list): - if flag_list[i] == flag_list[i-1]: - del flag_list[i] - else: - i += 1 - pre = '\n\tflags[' - post = '] = true;' - code = pre + string.join(flag_list, post + pre) + post - return code + def p_case_label_1(self, t): + 'case_label : DEFAULT' + t[0] = 'default' -# Assume all instruction flags are of the form 'IsFoo' -instFlagRE = re.compile(r'Is.*') + # + # The constant list for a decode case label must be non-empty, but + # may have one or more comma-separated integer literals in it. + # + def p_intlit_list_0(self, t): + 'intlit_list : INTLIT' + t[0] = [t[1]] -# OpClass constants end in 'Op' except No_OpClass -opClassRE = re.compile(r'.*Op|No_OpClass') + def p_intlit_list_1(self, t): + 'intlit_list : intlit_list COMMA INTLIT' + t[0] = t[1] + t[0].append(t[3]) -class InstObjParams(object): - def __init__(self, mnem, class_name, base_class = '', - snippets = {}, opt_args = []): - self.mnemonic = mnem - self.class_name = class_name - self.base_class = base_class - if not isinstance(snippets, dict): - snippets = {'code' : snippets} - compositeCode = ' '.join(map(str, snippets.values())) - self.snippets = snippets + # Define an instruction using the current instruction format + # (specified by an enclosing format block). + # "()" + def p_inst_0(self, t): + 'inst : ID LPAREN arg_list RPAREN' + # Pass the ID and arg list to the current format class to deal with. + currentFormat = formatStack.top() + codeObj = currentFormat.defineInst(t[1], t[3], t.lexer.lineno) + args = ','.join(map(str, t[3])) + args = re.sub('(?m)^', '//', args) + args = re.sub('^//', '', args) + comment = '\n// %s::%s(%s)\n' % (currentFormat.id, t[1], args) + codeObj.prepend_all(comment) + t[0] = codeObj - self.operands = OperandList(compositeCode) - self.constructor = self.operands.concatAttrStrings('constructor') - self.constructor += \ - '\n\t_numSrcRegs = %d;' % self.operands.numSrcRegs - self.constructor += \ - '\n\t_numDestRegs = %d;' % self.operands.numDestRegs - self.constructor += \ - '\n\t_numFPDestRegs = %d;' % self.operands.numFPDestRegs - self.constructor += \ - '\n\t_numIntDestRegs = %d;' % self.operands.numIntDestRegs - self.flags = self.operands.concatAttrLists('flags') + # Define an instruction using an explicitly specified format: + # "::()" + def p_inst_1(self, t): + 'inst : ID DBLCOLON ID LPAREN arg_list RPAREN' + try: + format = formatMap[t[1]] + except KeyError: + error(t.lexer.lineno, + 'instruction format "%s" not defined.' % t[1]) + codeObj = format.defineInst(t[3], t[5], t.lexer.lineno) + comment = '\n// %s::%s(%s)\n' % (t[1], t[3], t[5]) + codeObj.prepend_all(comment) + t[0] = codeObj - # Make a basic guess on the operand class (function unit type). - # These are good enough for most cases, and can be overridden - # later otherwise. - if 'IsStore' in self.flags: - self.op_class = 'MemWriteOp' - elif 'IsLoad' in self.flags or 'IsPrefetch' in self.flags: - self.op_class = 'MemReadOp' - elif 'IsFloating' in self.flags: - self.op_class = 'FloatAddOp' - else: - self.op_class = 'IntAluOp' + # The arg list generates a tuple, where the first element is a + # list of the positional args and the second element is a dict + # containing the keyword args. + def p_arg_list_0(self, t): + 'arg_list : positional_arg_list COMMA keyword_arg_list' + t[0] = ( t[1], t[3] ) - # Optional arguments are assumed to be either StaticInst flags - # or an OpClass value. To avoid having to import a complete - # list of these values to match against, we do it ad-hoc - # with regexps. - for oa in opt_args: - if instFlagRE.match(oa): - self.flags.append(oa) - elif opClassRE.match(oa): - self.op_class = oa - else: - error(0, 'InstObjParams: optional arg "%s" not recognized ' - 'as StaticInst::Flag or OpClass.' % oa) + def p_arg_list_1(self, t): + 'arg_list : positional_arg_list' + t[0] = ( t[1], {} ) - # add flag initialization to contructor here to include - # any flags added via opt_args - self.constructor += makeFlagConstructor(self.flags) + def p_arg_list_2(self, t): + 'arg_list : keyword_arg_list' + t[0] = ( [], t[1] ) - # if 'IsFloating' is set, add call to the FP enable check - # function (which should be provided by isa_desc via a declare) - if 'IsFloating' in self.flags: - self.fp_enable_check = 'fault = checkFpEnableFault(xc);' - else: - self.fp_enable_check = '' + def p_positional_arg_list_0(self, t): + 'positional_arg_list : empty' + t[0] = [] -####################### -# -# Output file template -# + def p_positional_arg_list_1(self, t): + 'positional_arg_list : expr' + t[0] = [t[1]] -file_template = ''' -/* - * DO NOT EDIT THIS FILE!!! - * - * It was automatically generated from the ISA description in %(filename)s - */ + def p_positional_arg_list_2(self, t): + 'positional_arg_list : positional_arg_list COMMA expr' + t[0] = t[1] + [t[3]] -%(includes)s + def p_keyword_arg_list_0(self, t): + 'keyword_arg_list : keyword_arg' + t[0] = t[1] -%(global_output)s + def p_keyword_arg_list_1(self, t): + 'keyword_arg_list : keyword_arg_list COMMA keyword_arg' + t[0] = t[1] + t[0].update(t[3]) -namespace %(namespace)s { + def p_keyword_arg(self, t): + 'keyword_arg : ID EQUALS expr' + t[0] = { t[1] : t[3] } -%(namespace_output)s + # + # Basic expressions. These constitute the argument values of + # "function calls" (i.e. instruction definitions in the decode + # block) and default values for formal parameters of format + # functions. + # + # Right now, these are either strings, integers, or (recursively) + # lists of exprs (using Python square-bracket list syntax). Note + # that bare identifiers are trated as string constants here (since + # there isn't really a variable namespace to refer to). + # + def p_expr_0(self, t): + '''expr : ID + | INTLIT + | STRLIT + | CODELIT''' + t[0] = t[1] -} // namespace %(namespace)s + def p_expr_1(self, t): + '''expr : LBRACKET list_expr RBRACKET''' + t[0] = t[2] -%(decode_function)s -''' + def p_list_expr_0(self, t): + 'list_expr : expr' + t[0] = [t[1]] -max_inst_regs_template = ''' -/* - * DO NOT EDIT THIS FILE!!! - * - * It was automatically generated from the ISA description in %(filename)s - */ + def p_list_expr_1(self, t): + 'list_expr : list_expr COMMA expr' + t[0] = t[1] + [t[3]] -namespace %(namespace)s { + def p_list_expr_2(self, t): + 'list_expr : empty' + t[0] = [] - const int MaxInstSrcRegs = %(MaxInstSrcRegs)d; - const int MaxInstDestRegs = %(MaxInstDestRegs)d; + # + # Empty production... use in other rules for readability. + # + def p_empty(self, t): + 'empty :' + pass -} // namespace %(namespace)s + # Parse error handler. Note that the argument here is the + # offending *token*, not a grammar symbol (hence the need to use + # t.value) + def p_error(self, t): + if t: + error(t.lexer.lineno, "syntax error at '%s'" % t.value) + else: + error(0, "unknown syntax error", True) -''' + # END OF GRAMMAR RULES +# Now build the parser. +parser = ISAParser() # Update the output file only if the new contents are different from # the current contents. Minimizes the files that need to be rebuilt -- cgit v1.2.3