X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Farch%2Fisa_parser.py;h=20df0a7dbc2ab58b34d78929989139932c6deca1;hb=6a05179e13a2ac8e72feb4bd00647013940d814e;hp=1b0d46410b9508f129507bbdb774b8efb094f31c;hpb=eae1e97fb002b44a9d8c46df2da1ddc1d0156ce4;p=gem5.git diff --git a/src/arch/isa_parser.py b/src/arch/isa_parser.py index 1b0d46410..20df0a7db 100755 --- a/src/arch/isa_parser.py +++ b/src/arch/isa_parser.py @@ -1,4 +1,17 @@ +# Copyright (c) 2014 ARM Limited +# All rights reserved +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# # Copyright (c) 2003-2005 The Regents of The University of Michigan +# Copyright (c) 2013,2015 Advanced Micro Devices, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -26,6 +39,7 @@ # # Authors: Steve Reinhardt +from __future__ import with_statement import os import sys import re @@ -74,42 +88,17 @@ def fixPythonIndentation(s): return s class ISAParserError(Exception): - """Error handler for parser errors""" + """Exception class for parser errors""" def __init__(self, first, second=None): if second is None: self.lineno = 0 self.string = first else: - if hasattr(first, 'lexer'): - first = first.lexer.lineno self.lineno = first self.string = second - def display(self, filename_stack, print_traceback=debug): - # 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). - - spaces = "" - for (filename, line) in filename_stack[:-1]: - print "%sIn file included from %s:" % (spaces, filename) - spaces += " " - - # Print a Python stack backtrace if requested. - if print_traceback or not self.lineno: - traceback.print_exc() - - line_str = "%s:" % (filename_stack[-1][0], ) - if self.lineno: - line_str += "%d:" % (self.lineno, ) - - return "%s%s %s" % (spaces, line_str, self.string) - - def exit(self, filename_stack, print_traceback=debug): - # Just call exit. - - sys.exit(self.display(filename_stack, print_traceback)) + def __str__(self): + return self.string def error(*args): raise ISAParserError(*args) @@ -174,6 +163,16 @@ class Template(object): if operands.readPC or operands.setPC: myDict['op_decl'] += 'TheISA::PCState __parserAutoPCState;\n' + # In case there are predicated register reads and write, declare + # the variables for register indicies. It is being assumed that + # all the operands in the OperandList are also in the + # SubOperandList and in the same order. Otherwise, it is + # expected that predication would not be used for the operands. + if operands.predRead: + myDict['op_decl'] += 'uint8_t _sourceIndex = 0;\n' + if operands.predWrite: + myDict['op_decl'] += 'uint8_t M5_VAR_USED _destIndex = 0;\n' + is_src = lambda op: op.is_src is_dest = lambda op: op.is_dest @@ -302,27 +301,29 @@ class GenCode(object): self.parser = parser self.header_output = parser.expandCpuSymbolsToString(header_output) self.decoder_output = parser.expandCpuSymbolsToString(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 = parser.expandCpuSymbolsToDict(exec_output) - self.decode_block = parser.expandCpuSymbolsToString(decode_block) + self.exec_output = exec_output + self.decode_block = decode_block self.has_decode_default = has_decode_default + # Write these code chunks out to the filesystem. They will be properly + # interwoven by the write_top_level_files(). + def emit(self): + if self.header_output: + self.parser.get_file('header').write(self.header_output) + if self.decoder_output: + self.parser.get_file('decoder').write(self.decoder_output) + if self.exec_output: + self.parser.get_file('exec').write(self.exec_output) + if self.decode_block: + self.parser.get_file('decode_block').write(self.decode_block) + # 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 self.parser.cpuModels: - n = cpu.name - exec_output[n] = self.exec_output[n] + other.exec_output[n] return GenCode(self.parser, self.header_output + other.header_output, self.decoder_output + other.decoder_output, - exec_output, + self.exec_output + other.exec_output, self.decode_block + other.decode_block, self.has_decode_default or other.has_decode_default) @@ -331,8 +332,7 @@ class GenCode(object): self.header_output = pre + self.header_output self.decoder_output = pre + self.decoder_output self.decode_block = pre + self.decode_block - for cpu in self.parser.cpuModels: - self.exec_output[cpu.name] = pre + self.exec_output[cpu.name] + self.exec_output = pre + self.exec_output # Wrap the decode block in a pair of strings (e.g., 'case foo:' # and 'break;'). Used to build the big nested switch statement. @@ -453,21 +453,23 @@ class Operand(object): # 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): + # time of __init__(). The register index enumeration is affected + # by predicated register reads/writes. Hence, we forward the flags + # that indicate whether or not predication is in use. + def finalize(self, predRead, predWrite): self.flags = self.getFlags() - self.constructor = self.makeConstructor() + self.constructor = self.makeConstructor(predRead, predWrite) self.op_decl = self.makeDecl() if self.is_src: - self.op_rd = self.makeRead() + self.op_rd = self.makeRead(predRead) self.op_src_decl = self.makeDecl() else: self.op_rd = '' self.op_src_decl = '' if self.is_dest: - self.op_wb = self.makeWrite() + self.op_wb = self.makeWrite(predWrite) self.op_dest_decl = self.makeDecl() else: self.op_wb = '' @@ -485,6 +487,9 @@ class Operand(object): def isIntReg(self): return 0 + def isCCReg(self): + return 0 + def isControlReg(self): return 0 @@ -494,6 +499,12 @@ class Operand(object): def isPCPart(self): return self.isPCState() and self.reg_spec + def hasReadPred(self): + return self.read_predicate != None + + def hasWritePred(self): + return self.write_predicate != None + def getFlags(self): # note the empty slice '[:]' gives us a copy of self.flags[0] # instead of a reference to it @@ -516,35 +527,68 @@ class IntRegOperand(Operand): def isIntReg(self): return 1 - def makeConstructor(self): - c = '' + def makeConstructor(self, predRead, predWrite): + c_src = '' + c_dest = '' + if self.is_src: - c += '\n\t_srcRegIdx[%d] = %s;' % \ - (self.src_reg_idx, self.reg_spec) + c_src = '\n\t_srcRegIdx[_numSrcRegs++] = %s;' % (self.reg_spec) + if self.hasReadPred(): + c_src = '\n\tif (%s) {%s\n\t}' % \ + (self.read_predicate, c_src) + if self.is_dest: - c += '\n\t_destRegIdx[%d] = %s;' % \ - (self.dest_reg_idx, self.reg_spec) - return c + c_dest = '\n\t_destRegIdx[_numDestRegs++] = %s;' % \ + (self.reg_spec) + c_dest += '\n\t_numIntDestRegs++;' + if self.hasWritePred(): + c_dest = '\n\tif (%s) {%s\n\t}' % \ + (self.write_predicate, c_dest) + + return c_src + c_dest - def makeRead(self): + def makeRead(self, predRead): if (self.ctype == 'float' or self.ctype == 'double'): error('Attempt to read integer register as FP') if self.read_code != None: return self.buildReadCode('readIntRegOperand') - int_reg_val = 'xc->readIntRegOperand(this, %d)' % self.src_reg_idx + + int_reg_val = '' + if predRead: + int_reg_val = 'xc->readIntRegOperand(this, _sourceIndex++)' + if self.hasReadPred(): + int_reg_val = '(%s) ? %s : 0' % \ + (self.read_predicate, int_reg_val) + else: + int_reg_val = 'xc->readIntRegOperand(this, %d)' % self.src_reg_idx + return '%s = %s;\n' % (self.base_name, int_reg_val) - def makeWrite(self): + def makeWrite(self, predWrite): if (self.ctype == 'float' or self.ctype == 'double'): error('Attempt to write integer register as FP') if self.write_code != None: return self.buildWriteCode('setIntRegOperand') + + if predWrite: + wp = 'true' + if self.hasWritePred(): + wp = self.write_predicate + + wcond = 'if (%s)' % (wp) + windex = '_destIndex++' + else: + wcond = '' + windex = '%d' % self.dest_reg_idx + wb = ''' + %s { %s final_val = %s; - xc->setIntRegOperand(this, %d, final_val);\n + xc->setIntRegOperand(this, %s, final_val);\n if (traceData) { traceData->setData(final_val); } - }''' % (self.ctype, self.base_name, self.dest_reg_idx) + }''' % (wcond, self.ctype, self.base_name, windex) + return wb class FloatRegOperand(Operand): @@ -554,17 +598,23 @@ class FloatRegOperand(Operand): def isFloatReg(self): return 1 - def makeConstructor(self): - c = '' + def makeConstructor(self, predRead, predWrite): + c_src = '' + c_dest = '' + if self.is_src: - c += '\n\t_srcRegIdx[%d] = %s + FP_Base_DepTag;' % \ - (self.src_reg_idx, self.reg_spec) + c_src = '\n\t_srcRegIdx[_numSrcRegs++] = %s + FP_Reg_Base;' % \ + (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 + c_dest = \ + '\n\t_destRegIdx[_numDestRegs++] = %s + FP_Reg_Base;' % \ + (self.reg_spec) + c_dest += '\n\t_numFPDestRegs++;' + + return c_src + c_dest - def makeRead(self): + def makeRead(self, predRead): bit_select = 0 if (self.ctype == 'float' or self.ctype == 'double'): func = 'readFloatRegOperand' @@ -572,22 +622,108 @@ class FloatRegOperand(Operand): func = 'readFloatRegOperandBits' if self.read_code != None: return self.buildReadCode(func) - return '%s = xc->%s(this, %d);\n' % \ - (self.base_name, func, self.src_reg_idx) - def makeWrite(self): + if predRead: + rindex = '_sourceIndex++' + else: + rindex = '%d' % self.src_reg_idx + + return '%s = xc->%s(this, %s);\n' % \ + (self.base_name, func, rindex) + + def makeWrite(self, predWrite): if (self.ctype == 'float' or self.ctype == 'double'): func = 'setFloatRegOperand' else: func = 'setFloatRegOperandBits' if self.write_code != None: return self.buildWriteCode(func) + + if predWrite: + wp = '_destIndex++' + else: + wp = '%d' % self.dest_reg_idx + wp = 'xc->%s(this, %s, final_val);' % (func, wp) + wb = ''' { %s final_val = %s; - xc->%s(this, %d, final_val);\n + %s\n if (traceData) { traceData->setData(final_val); } - }''' % (self.ctype, self.base_name, func, self.dest_reg_idx) + }''' % (self.ctype, self.base_name, wp) + return wb + +class CCRegOperand(Operand): + def isReg(self): + return 1 + + def isCCReg(self): + return 1 + + def makeConstructor(self, predRead, predWrite): + c_src = '' + c_dest = '' + + if self.is_src: + c_src = '\n\t_srcRegIdx[_numSrcRegs++] = %s + CC_Reg_Base;' % \ + (self.reg_spec) + if self.hasReadPred(): + c_src = '\n\tif (%s) {%s\n\t}' % \ + (self.read_predicate, c_src) + + if self.is_dest: + c_dest = \ + '\n\t_destRegIdx[_numDestRegs++] = %s + CC_Reg_Base;' % \ + (self.reg_spec) + c_dest += '\n\t_numCCDestRegs++;' + if self.hasWritePred(): + c_dest = '\n\tif (%s) {%s\n\t}' % \ + (self.write_predicate, c_dest) + + return c_src + c_dest + + def makeRead(self, predRead): + if (self.ctype == 'float' or self.ctype == 'double'): + error('Attempt to read condition-code register as FP') + if self.read_code != None: + return self.buildReadCode('readCCRegOperand') + + int_reg_val = '' + if predRead: + int_reg_val = 'xc->readCCRegOperand(this, _sourceIndex++)' + if self.hasReadPred(): + int_reg_val = '(%s) ? %s : 0' % \ + (self.read_predicate, int_reg_val) + else: + int_reg_val = 'xc->readCCRegOperand(this, %d)' % self.src_reg_idx + + return '%s = %s;\n' % (self.base_name, int_reg_val) + + def makeWrite(self, predWrite): + if (self.ctype == 'float' or self.ctype == 'double'): + error('Attempt to write condition-code register as FP') + if self.write_code != None: + return self.buildWriteCode('setCCRegOperand') + + if predWrite: + wp = 'true' + if self.hasWritePred(): + wp = self.write_predicate + + wcond = 'if (%s)' % (wp) + windex = '_destIndex++' + else: + wcond = '' + windex = '%d' % self.dest_reg_idx + + wb = ''' + %s + { + %s final_val = %s; + xc->setCCRegOperand(this, %s, final_val);\n + if (traceData) { traceData->setData(final_val); } + }''' % (wcond, self.ctype, self.base_name, windex) + return wb class ControlRegOperand(Operand): @@ -597,41 +733,60 @@ class ControlRegOperand(Operand): def isControlReg(self): return 1 - def makeConstructor(self): - c = '' + def makeConstructor(self, predRead, predWrite): + c_src = '' + c_dest = '' + if self.is_src: - c += '\n\t_srcRegIdx[%d] = %s + Ctrl_Base_DepTag;' % \ - (self.src_reg_idx, self.reg_spec) + c_src = \ + '\n\t_srcRegIdx[_numSrcRegs++] = %s + Misc_Reg_Base;' % \ + (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 + c_dest = \ + '\n\t_destRegIdx[_numDestRegs++] = %s + Misc_Reg_Base;' % \ + (self.reg_spec) - def makeRead(self): + return c_src + c_dest + + def makeRead(self, predRead): bit_select = 0 if (self.ctype == 'float' or self.ctype == 'double'): error('Attempt to read control register as FP') if self.read_code != None: return self.buildReadCode('readMiscRegOperand') + + if predRead: + rindex = '_sourceIndex++' + else: + rindex = '%d' % self.src_reg_idx + return '%s = xc->readMiscRegOperand(this, %s);\n' % \ - (self.base_name, self.src_reg_idx) + (self.base_name, rindex) - def makeWrite(self): + def makeWrite(self, predWrite): if (self.ctype == 'float' or self.ctype == 'double'): error('Attempt to write control register as FP') if self.write_code != None: return self.buildWriteCode('setMiscRegOperand') + + if predWrite: + windex = '_destIndex++' + else: + windex = '%d' % self.dest_reg_idx + wb = 'xc->setMiscRegOperand(this, %s, %s);\n' % \ - (self.dest_reg_idx, self.base_name) + (windex, 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): + def makeConstructor(self, predRead, predWrite): return '' def makeDecl(self): @@ -640,21 +795,21 @@ class MemOperand(Operand): # Declare memory data variable. return '%s %s = 0;\n' % (self.ctype, self.base_name) - def makeRead(self): + def makeRead(self, predRead): if self.read_code != None: return self.buildReadCode() return '' - def makeWrite(self): + def makeWrite(self, predWrite): if self.write_code != None: return self.buildWriteCode() return '' class PCStateOperand(Operand): - def makeConstructor(self): + def makeConstructor(self, predRead, predWrite): return '' - def makeRead(self): + def makeRead(self, predRead): if self.reg_spec: # A component of the PC state. return '%s = __parserAutoPCState.%s();\n' % \ @@ -663,7 +818,7 @@ class PCStateOperand(Operand): # The whole PC state itself. return '%s = xc->pcState();\n' % self.base_name - def makeWrite(self): + def makeWrite(self, predWrite): if self.reg_spec: # A component of the PC state. return '__parserAutoPCState.%s(%s);\n' % \ @@ -676,7 +831,9 @@ class PCStateOperand(Operand): ctype = 'TheISA::PCState' if self.isPCPart(): ctype = self.ctype - return "%s %s;\n" % (ctype, self.base_name) + # Note that initializations in the declarations are solely + # to avoid 'uninitialized variable' errors from the compiler. + return '%s %s = 0;\n' % (ctype, self.base_name) def isPCState(self): return 1 @@ -726,7 +883,15 @@ class OperandList(object): self.numDestRegs = 0 self.numFPDestRegs = 0 self.numIntDestRegs = 0 + self.numCCDestRegs = 0 + self.numMiscDestRegs = 0 self.memOperand = None + + # Flags to keep track if one or more operands are to be read/written + # conditionally. + self.predRead = False + self.predWrite = False + for op_desc in self.items: if op_desc.isReg(): if op_desc.is_src: @@ -739,18 +904,31 @@ class OperandList(object): self.numFPDestRegs += 1 elif op_desc.isIntReg(): self.numIntDestRegs += 1 + elif op_desc.isCCReg(): + self.numCCDestRegs += 1 + elif op_desc.isControlReg(): + self.numMiscDestRegs += 1 elif op_desc.isMem(): if self.memOperand: error("Code block has more than one memory operand.") self.memOperand = op_desc + + # Check if this operand has read/write predication. If true, then + # the microop will dynamically index source/dest registers. + self.predRead = self.predRead or op_desc.hasReadPred() + self.predWrite = self.predWrite or op_desc.hasWritePred() + if parser.maxInstSrcRegs < self.numSrcRegs: parser.maxInstSrcRegs = self.numSrcRegs if parser.maxInstDestRegs < self.numDestRegs: parser.maxInstDestRegs = self.numDestRegs + if parser.maxMiscDestRegs < self.numMiscDestRegs: + parser.maxMiscDestRegs = self.numMiscDestRegs + # now make a final pass to finalize op_desc fields that may depend # on the register enumeration for op_desc in self.items: - op_desc.finalize() + op_desc.finalize(self.predRead, self.predWrite) def __len__(self): return len(self.items) @@ -819,8 +997,8 @@ class SubOperandList(OperandList): # find this op in the master list op_desc = master_list.find_base(op_base) if not op_desc: - error('Found operand %s which is not in the master list!' \ - ' This is an internal error' % op_base) + error('Found operand %s which is not in the master list!' + % op_base) else: # See if we've already found this operand op_desc = self.find_base(op_base) @@ -840,22 +1018,35 @@ class SubOperandList(OperandList): # Whether this instruction manipulates the whole PC or parts of it. # Mixing the two is a bad idea and flagged as an error. self.pcPart = None + + # Flags to keep track if one or more operands are to be read/written + # conditionally. + self.predRead = False + self.predWrite = False + for op_desc in self.items: if op_desc.isPCPart(): self.readPC = True if op_desc.is_dest: self.setPC = True + if op_desc.isPCState(): if self.pcPart is not None: if self.pcPart and not op_desc.isPCPart() or \ not self.pcPart and op_desc.isPCPart(): error("Mixed whole and partial PC state operands.") self.pcPart = op_desc.isPCPart() + if op_desc.isMem(): if self.memOperand: error("Code block has more than one memory operand.") self.memOperand = op_desc + # Check if this operand has read/write predication. If true, then + # the microop will dynamically index source/dest registers. + self.predRead = self.predRead or op_desc.hasReadPred() + self.predWrite = self.predWrite or op_desc.hasWritePred() + # Regular expression object to match C++ strings stringRE = re.compile(r'"([^"\\]|\\.)*"') @@ -902,28 +1093,22 @@ class InstObjParams(object): self.snippets = snippets self.operands = OperandList(parser, 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 + + # The header of the constructor declares the variables to be used + # in the body of the constructor. + header = '' + header += '\n\t_numSrcRegs = 0;' + header += '\n\t_numDestRegs = 0;' + header += '\n\t_numFPDestRegs = 0;' + header += '\n\t_numIntDestRegs = 0;' + header += '\n\t_numCCDestRegs = 0;' + + self.constructor = header + \ + self.operands.concatAttrStrings('constructor') + self.flags = self.operands.concatAttrLists('flags') - # 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' + self.op_class = None # Optional arguments are assumed to be either StaticInst flags # or an OpClass value. To avoid having to import a complete @@ -938,6 +1123,18 @@ class InstObjParams(object): error('InstObjParams: optional arg "%s" not recognized ' 'as StaticInst::Flag or OpClass.' % oa) + # Make a basic guess on the operand class if not set. + # These are good enough for most cases. + if not self.op_class: + 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' + # add flag initialization to contructor here to include # any flags added via opt_args self.constructor += makeFlagConstructor(self.flags) @@ -965,53 +1162,65 @@ class Stack(list): def top(self): return self[-1] +# Format a file include stack backtrace as a string +def backtrace(filename_stack): + fmt = "In file included from %s:" + return "\n".join([fmt % f for f in filename_stack]) + + ####################### # -# Output file template +# LineTracker: track filenames along with line numbers in PLY lineno fields +# PLY explicitly doesn't do anything with 'lineno' except propagate +# it. This class lets us tie filenames with the line numbers with a +# minimum of disruption to existing increment code. # -file_template = ''' -/* - * DO NOT EDIT THIS FILE!!! - * - * It was automatically generated from the ISA description in %(filename)s - */ - -%(includes)s - -%(global_output)s +class LineTracker(object): + def __init__(self, filename, lineno=1): + self.filename = filename + self.lineno = lineno -namespace %(namespace)s { + # Overload '+=' for increments. We need to create a new object on + # each update else every token ends up referencing the same + # constantly incrementing instance. + def __iadd__(self, incr): + return LineTracker(self.filename, self.lineno + incr) -%(namespace_output)s - -} // namespace %(namespace)s - -%(decode_function)s -''' - -max_inst_regs_template = ''' -/* - * DO NOT EDIT THIS FILE!!! - * - * It was automatically generated from the ISA description in %(filename)s - */ - -namespace %(namespace)s { + def __str__(self): + return "%s:%d" % (self.filename, self.lineno) - const int MaxInstSrcRegs = %(MaxInstSrcRegs)d; - const int MaxInstDestRegs = %(MaxInstDestRegs)d; + # In case there are places where someone really expects a number + def __int__(self): + return self.lineno -} // namespace %(namespace)s -''' +####################### +# +# ISA Parser +# parses ISA DSL and emits C++ headers and source +# class ISAParser(Grammar): - def __init__(self, output_dir, cpu_models): + class CpuModel(object): + def __init__(self, name, filename, includes, strings): + self.name = name + self.filename = filename + self.includes = includes + self.strings = strings + + def __init__(self, output_dir): super(ISAParser, self).__init__() self.output_dir = output_dir - self.cpuModels = cpu_models + self.filename = None # for output file watermarking/scaremongering + + self.cpuModels = [ + ISAParser.CpuModel('ExecContext', + 'generic_cpu_exec.cc', + '#include "cpu/exec_context.hh"', + { "CPU_exec_context" : "ExecContext" }), + ] # variable to hold templates self.templateMap = {} @@ -1019,6 +1228,16 @@ class ISAParser(Grammar): # This dictionary maps format name strings to Format objects. self.formatMap = {} + # Track open files and, if applicable, how many chunks it has been + # split into so far. + self.files = {} + self.splits = {} + + # isa_name / namespace identifier from namespace declaration. + # before the namespace declaration, None. + self.isa_name = None + self.namespace = None + # The format stack. self.formatStack = Stack(NoFormat()) @@ -1036,6 +1255,182 @@ class ISAParser(Grammar): self.maxInstSrcRegs = 0 self.maxInstDestRegs = 0 + self.maxMiscDestRegs = 0 + + def __getitem__(self, i): # Allow object (self) to be + return getattr(self, i) # passed to %-substitutions + + # Change the file suffix of a base filename: + # (e.g.) decoder.cc -> decoder-g.cc.inc for 'global' outputs + def suffixize(self, s, sec): + extn = re.compile('(\.[^\.]+)$') # isolate extension + if self.namespace: + return extn.sub(r'-ns\1.inc', s) # insert some text on either side + else: + return extn.sub(r'-g\1.inc', s) + + # Get the file object for emitting code into the specified section + # (header, decoder, exec, decode_block). + def get_file(self, section): + if section == 'decode_block': + filename = 'decode-method.cc.inc' + else: + if section == 'header': + file = 'decoder.hh' + else: + file = '%s.cc' % section + filename = self.suffixize(file, section) + try: + return self.files[filename] + except KeyError: pass + + f = self.open(filename) + self.files[filename] = f + + # The splittable files are the ones with many independent + # per-instruction functions - the decoder's instruction constructors + # and the instruction execution (execute()) methods. These both have + # the suffix -ns.cc.inc, meaning they are within the namespace part + # of the ISA, contain object-emitting C++ source, and are included + # into other top-level files. These are the files that need special + # #define's to allow parts of them to be compiled separately. Rather + # than splitting the emissions into separate files, the monolithic + # output of the ISA parser is maintained, but the value (or lack + # thereof) of the __SPLIT definition during C preprocessing will + # select the different chunks. If no 'split' directives are used, + # the cpp emissions have no effect. + if re.search('-ns.cc.inc$', filename): + print >>f, '#if !defined(__SPLIT) || (__SPLIT == 1)' + self.splits[f] = 1 + # ensure requisite #include's + elif filename in ['decoder-g.cc.inc', 'exec-g.cc.inc']: + print >>f, '#include "decoder.hh"' + elif filename == 'decoder-g.hh.inc': + print >>f, '#include "base/bitfield.hh"' + + return f + + # Weave together the parts of the different output sections by + # #include'ing them into some very short top-level .cc/.hh files. + # These small files make it much clearer how this tool works, since + # you directly see the chunks emitted as files that are #include'd. + def write_top_level_files(self): + dep = self.open('inc.d', bare=True) + + # decoder header - everything depends on this + file = 'decoder.hh' + with self.open(file) as f: + inc = [] + + fn = 'decoder-g.hh.inc' + assert(fn in self.files) + f.write('#include "%s"\n' % fn) + inc.append(fn) + + fn = 'decoder-ns.hh.inc' + assert(fn in self.files) + f.write('namespace %s {\n#include "%s"\n}\n' + % (self.namespace, fn)) + inc.append(fn) + + print >>dep, file+':', ' '.join(inc) + + # decoder method - cannot be split + file = 'decoder.cc' + with self.open(file) as f: + inc = [] + + fn = 'decoder-g.cc.inc' + assert(fn in self.files) + f.write('#include "%s"\n' % fn) + inc.append(fn) + + fn = 'decode-method.cc.inc' + # is guaranteed to have been written for parse to complete + f.write('#include "%s"\n' % fn) + inc.append(fn) + + inc.append("decoder.hh") + print >>dep, file+':', ' '.join(inc) + + extn = re.compile('(\.[^\.]+)$') + + # instruction constructors + splits = self.splits[self.get_file('decoder')] + file_ = 'inst-constrs.cc' + for i in range(1, splits+1): + if splits > 1: + file = extn.sub(r'-%d\1' % i, file_) + else: + file = file_ + with self.open(file) as f: + inc = [] + + fn = 'decoder-g.cc.inc' + assert(fn in self.files) + f.write('#include "%s"\n' % fn) + inc.append(fn) + + fn = 'decoder-ns.cc.inc' + assert(fn in self.files) + print >>f, 'namespace %s {' % self.namespace + if splits > 1: + print >>f, '#define __SPLIT %u' % i + print >>f, '#include "%s"' % fn + print >>f, '}' + inc.append(fn) + + inc.append("decoder.hh") + print >>dep, file+':', ' '.join(inc) + + # instruction execution per-CPU model + splits = self.splits[self.get_file('exec')] + for cpu in self.cpuModels: + for i in range(1, splits+1): + if splits > 1: + file = extn.sub(r'_%d\1' % i, cpu.filename) + else: + file = cpu.filename + with self.open(file) as f: + inc = [] + + fn = 'exec-g.cc.inc' + assert(fn in self.files) + f.write('#include "%s"\n' % fn) + inc.append(fn) + + f.write(cpu.includes+"\n") + + fn = 'exec-ns.cc.inc' + assert(fn in self.files) + print >>f, 'namespace %s {' % self.namespace + print >>f, '#define CPU_EXEC_CONTEXT %s' \ + % cpu.strings['CPU_exec_context'] + if splits > 1: + print >>f, '#define __SPLIT %u' % i + print >>f, '#include "%s"' % fn + print >>f, '}' + inc.append(fn) + + inc.append("decoder.hh") + print >>dep, file+':', ' '.join(inc) + + # max_inst_regs.hh + self.update('max_inst_regs.hh', + '''namespace %(namespace)s { + const int MaxInstSrcRegs = %(maxInstSrcRegs)d; + const int MaxInstDestRegs = %(maxInstDestRegs)d; + const int MaxMiscDestRegs = %(maxMiscDestRegs)d;\n}\n''' % self) + print >>dep, 'max_inst_regs.hh:' + + dep.close() + + + scaremonger_template ='''// DO NOT EDIT +// This file was automatically generated from an ISA description: +// %(filename)s + +'''; ##################################################################### # @@ -1058,7 +1453,7 @@ class ISAParser(Grammar): reserved = ( 'BITFIELD', 'DECODE', 'DECODER', 'DEFAULT', 'DEF', 'EXEC', 'FORMAT', 'HEADER', 'LET', 'NAMESPACE', 'OPERAND_TYPES', 'OPERANDS', - 'OUTPUT', 'SIGNED', 'TEMPLATE' + 'OUTPUT', 'SIGNED', 'SPLIT', 'TEMPLATE' ) # List of tokens. The lex module requires this. @@ -1128,7 +1523,7 @@ class ISAParser(Grammar): try: t.value = int(t.value,0) except ValueError: - error(t, 'Integer value "%s" too large' % t.value) + error(t.lexer.lineno, 'Integer value "%s" too large' % t.value) t.value = 0 return t @@ -1157,13 +1552,13 @@ class ISAParser(Grammar): return t def t_NEWFILE(self, t): - r'^\#\#newfile\s+"[^"]*"' - self.fileNameStack.push((t.value[11:-1], t.lexer.lineno)) - t.lexer.lineno = 0 + r'^\#\#newfile\s+"[^"]*"\n' + self.fileNameStack.push(t.lexer.lineno) + t.lexer.lineno = LineTracker(t.value[11:-2]) def t_ENDFILE(self, t): - r'^\#\#endfile' - (old_filename, t.lexer.lineno) = self.fileNameStack.pop() + r'^\#\#endfile\n' + t.lexer.lineno = self.fileNameStack.pop() # # The functions t_NEWLINE, t_ignore, and t_error are @@ -1184,7 +1579,7 @@ class ISAParser(Grammar): # Error handler def t_error(self, t): - error(t, "illegal character '%s'" % t.value[0]) + error(t.lexer.lineno, "illegal character '%s'" % t.value[0]) t.skip(1) ##################################################################### @@ -1211,60 +1606,82 @@ class ISAParser(Grammar): # 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::Decoder::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) + 'specification : opt_defs_and_outputs top_level_decode_block' - # ISA name declaration looks like "namespace ;" - def p_name_decl(self, t): - 'name_decl : NAMESPACE ID SEMI' - t[0] = t[2] + for f in self.splits.iterkeys(): + f.write('\n#endif\n') + + for f in self.files.itervalues(): # close ALL the files; + f.close() # not doing so can cause compilation to fail + + self.write_top_level_files() + + t[0] = True - # 'opt_defs_and_outputs' is a possibly empty sequence of - # def and/or output statements. + # 'opt_defs_and_outputs' is a possibly empty sequence of def and/or + # output statements. Its productions do the hard work of eventually + # instantiating a GenCode, which are generally emitted (written to disk) + # as soon as possible, except for the decode_block, which has to be + # accumulated into one large function of nested switch/case blocks. def p_opt_defs_and_outputs_0(self, t): 'opt_defs_and_outputs : empty' - t[0] = GenCode(self) 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] # The list of possible definition/output statements. + # They are all processed as they are seen. def p_def_or_output(self, t): - '''def_or_output : def_format + '''def_or_output : name_decl + | def_format | def_bitfield | def_bitfield_struct | def_template | def_operand_types | def_operands - | output_header - | output_decoder - | output_exec - | global_let''' + | output + | global_let + | split''' + + # Utility function used by both invocations of splitting - explicit + # 'split' keyword and split() function inside "let {{ }};" blocks. + def split(self, sec, write=False): + assert(sec != 'header' and "header cannot be split") + + f = self.get_file(sec) + self.splits[f] += 1 + s = '\n#endif\n#if __SPLIT == %u\n' % self.splits[f] + if write: + f.write(s) + else: + return s + + # split output file to reduce compilation time + def p_split(self, t): + 'split : SPLIT output_type SEMI' + assert(self.isa_name and "'split' not allowed before namespace decl") + + self.split(t[2], True) + + def p_output_type(self, t): + '''output_type : DECODER + | HEADER + | EXEC''' t[0] = t[1] + # ISA name declaration looks like "namespace ;" + def p_name_decl(self, t): + 'name_decl : NAMESPACE ID SEMI' + assert(self.isa_name == None and "Only 1 namespace decl permitted") + self.isa_name = t[2] + self.namespace = t[2] + 'Inst' + # Output blocks 'output {{...}}' (C++ code blocks) are copied # directly to the appropriate output section. @@ -1279,17 +1696,10 @@ StaticInstPtr s = self.protectCpuSymbols(s) return substBitOps(s % self.templateMap) - def p_output_header(self, t): - 'output_header : OUTPUT HEADER CODELIT SEMI' - t[0] = GenCode(self, header_output = self.process_output(t[3])) - - def p_output_decoder(self, t): - 'output_decoder : OUTPUT DECODER CODELIT SEMI' - t[0] = GenCode(self, decoder_output = self.process_output(t[3])) - - def p_output_exec(self, t): - 'output_exec : OUTPUT EXEC CODELIT SEMI' - t[0] = GenCode(self, exec_output = self.process_output(t[3])) + def p_output(self, t): + 'output : OUTPUT output_type CODELIT SEMI' + kwargs = { t[2]+'_output' : self.process_output(t[3]) } + GenCode(self, **kwargs).emit() # global let blocks 'let {{...}}' (Python code blocks) are # executed directly when seen. Note that these execute in a @@ -1297,22 +1707,40 @@ StaticInstPtr # from polluting this script's namespace. def p_global_let(self, t): 'global_let : LET CODELIT SEMI' + def _split(sec): + return self.split(sec) self.updateExportContext() self.exportContext["header_output"] = '' self.exportContext["decoder_output"] = '' self.exportContext["exec_output"] = '' self.exportContext["decode_block"] = '' + self.exportContext["split"] = _split + split_setup = ''' +def wrap(func): + def split(sec): + globals()[sec + '_output'] += func(sec) + return split +split = wrap(split) +del wrap +''' + # This tricky setup (immediately above) allows us to just write + # (e.g.) "split('exec')" in the Python code and the split #ifdef's + # will automatically be added to the exec_output variable. The inner + # Python execution environment doesn't know about the split points, + # so we carefully inject and wrap a closure that can retrieve the + # next split's #define from the parser and add it to the current + # emission-in-progress. try: - exec fixPythonIndentation(t[2]) in self.exportContext + exec split_setup+fixPythonIndentation(t[2]) in self.exportContext except Exception, exc: if debug: raise - error(t, 'error: %s in global let block "%s".' % (exc, t[2])) - t[0] = GenCode(self, - header_output=self.exportContext["header_output"], - decoder_output=self.exportContext["decoder_output"], - exec_output=self.exportContext["exec_output"], - decode_block=self.exportContext["decode_block"]) + error(t.lineno(1), 'In global let block: %s' % exc) + GenCode(self, + header_output=self.exportContext["header_output"], + decoder_output=self.exportContext["decoder_output"], + exec_output=self.exportContext["exec_output"], + decode_block=self.exportContext["decode_block"]).emit() # Define the mapping from operand type extensions to C++ types and # bit widths (stored in operandTypeMap). @@ -1323,24 +1751,23 @@ StaticInstPtr except Exception, exc: if debug: raise - error(t, - 'error: %s in def operand_types block "%s".' % (exc, t[3])) - t[0] = GenCode(self) # contributes nothing to the output C++ file + error(t.lineno(1), + 'In def operand_types: %s' % exc) # 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 hasattr(self, 'operandTypeMap'): - error(t, 'error: operand types must be defined before operands') + error(t.lineno(1), + 'error: operand types must be defined before operands') try: user_dict = eval('{' + t[3] + '}', self.exportContext) except Exception, exc: if debug: raise - error(t, 'error: %s in def operands block "%s".' % (exc, t[3])) + error(t.lineno(1), 'In def operands: %s' % exc) self.buildOperandNameMap(user_dict, t.lexer.lineno) - t[0] = GenCode(self) # contributes nothing to the output C++ file # A bitfield definition looks like: # 'def [signed] bitfield [:]' @@ -1351,7 +1778,7 @@ StaticInstPtr 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(self, header_output=hash_define) + GenCode(self, header_output=hash_define).emit() # alternate form for single bit: 'def [signed] bitfield []' def p_def_bitfield_1(self, t): @@ -1360,16 +1787,17 @@ StaticInstPtr 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(self, header_output=hash_define) + GenCode(self, header_output=hash_define).emit() # 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, 'error: structure bitfields are always unsigned.') + error(t.lineno(1), + '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(self, header_output=hash_define) + GenCode(self, header_output=hash_define).emit() def p_id_with_dot_0(self, t): 'id_with_dot : ID' @@ -1389,8 +1817,9 @@ StaticInstPtr def p_def_template(self, t): 'def_template : DEF TEMPLATE ID CODELIT SEMI' + if t[3] in self.templateMap: + print "warning: template %s already defined" % t[3] self.templateMap[t[3]] = Template(self, t[4]) - t[0] = GenCode(self) # An instruction format definition looks like # "def format () {{...}};" @@ -1398,7 +1827,6 @@ StaticInstPtr 'def_format : DEF FORMAT ID LPAREN param_list RPAREN CODELIT SEMI' (id, params, code) = (t[3], t[5], t[7]) self.defFormat(id, params, code, t.lexer.lineno) - t[0] = GenCode(self) # The formal parameter list for an instruction format is a # possibly empty list of comma-separated parameters. Positional @@ -1469,6 +1897,18 @@ StaticInstPtr # A decode block looks like: # decode [, ]* [default ] { ... } # + def p_top_level_decode_block(self, t): + 'top_level_decode_block : decode_block' + codeObj = t[1] + codeObj.wrap_decode_block(''' +StaticInstPtr +%(isa_name)s::Decoder::decodeInst(%(isa_name)s::ExtMachInst machInst) +{ + using namespace %(namespace)s; +''' % self, '}') + + codeObj.emit() + def p_decode_block(self, t): 'decode_block : DECODE ID opt_default LBRACE decode_stmt_list RBRACE' default_defaults = self.defaultStack.pop() @@ -1508,7 +1948,7 @@ StaticInstPtr 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, 'Two default cases in decode block') + error(t.lineno(1), 'Two default cases in decode block') t[0] = t[1] + t[2] # @@ -1555,56 +1995,63 @@ StaticInstPtr self.formatStack.push(self.formatMap[t[1]]) t[0] = ('', '// format %s' % t[1]) except KeyError: - error(t, 'instruction format "%s" not defined.' % t[1]) + error(t.lineno(1), 'instruction format "%s" not defined.' % t[1]) # Nested decode block: if the value of the current field matches - # the specified constant, do a nested decode on some other field. + # the specified constant(s), 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] + 'decode_stmt : case_list COLON decode_block' + case_list = 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') + codeObj.wrap_decode_block('\n%s\n' % ''.join(case_list)) + codeObj.has_decode_default = (case_list == ['default:']) t[0] = codeObj # Instruction definition (finally!). def p_decode_stmt_inst(self, t): - 'decode_stmt : case_label COLON inst SEMI' - label = t[1] + 'decode_stmt : case_list COLON inst SEMI' + case_list = t[1] codeObj = t[3] - codeObj.wrap_decode_block('\n%s:' % label, 'break;\n') - codeObj.has_decode_default = (label == 'default') + codeObj.wrap_decode_block('\n%s' % ''.join(case_list), 'break;\n') + codeObj.has_decode_default = (case_list == ['default:']) t[0] = codeObj - # 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])) + # The constant list for a decode case label must be non-empty, and must + # either be the keyword 'default', or made up of one or more + # comma-separated integer literals or strings which evaluate to + # constants when compiled as C++. + def p_case_list_0(self, t): + 'case_list : DEFAULT' + t[0] = ['default:'] + + def prep_int_lit_case_label(self, lit): + if lit >= 2**32: + return 'case ULL(%#x): ' % lit + else: + return 'case %#x: ' % lit - def p_case_label_1(self, t): - 'case_label : DEFAULT' - t[0] = 'default' + def prep_str_lit_case_label(self, lit): + return 'case %s: ' % lit - # - # 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]] + def p_case_list_1(self, t): + 'case_list : INTLIT' + t[0] = [self.prep_int_lit_case_label(t[1])] + + def p_case_list_2(self, t): + 'case_list : STRLIT' + t[0] = [self.prep_str_lit_case_label(t[1])] + + def p_case_list_3(self, t): + 'case_list : case_list COMMA INTLIT' + t[0] = t[1] + t[0].append(self.prep_int_lit_case_label(t[3])) - def p_intlit_list_1(self, t): - 'intlit_list : intlit_list COMMA INTLIT' + def p_case_list_4(self, t): + 'case_list : case_list COMMA STRLIT' t[0] = t[1] - t[0].append(t[3]) + t[0].append(self.prep_str_lit_case_label(t[3])) # Define an instruction using the current instruction format # (specified by an enclosing format block). @@ -1628,7 +2075,7 @@ StaticInstPtr try: format = self.formatMap[t[1]] except KeyError: - error(t, 'instruction format "%s" not defined.' % t[1]) + error(t.lineno(1), 'instruction format "%s" not defined.' % t[1]) codeObj = format.defineInst(self, t[3], t[5], t.lexer.lineno) comment = '\n// %s::%s(%s)\n' % (t[1], t[3], t[5]) @@ -1721,7 +2168,7 @@ StaticInstPtr # t.value) def p_error(self, t): if t: - error(t, "syntax error at '%s'" % t.value) + error(t.lexer.lineno, "syntax error at '%s'" % t.value) else: error("unknown syntax error") @@ -1788,20 +2235,17 @@ StaticInstPtr def buildOperandNameMap(self, user_dict, lineno): operand_name = {} 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"' % + + # Check if extra attributes have been specified. + if len(val) > 9: + error(lineno, 'error: too many attributes for operand "%s"' % base_cls_name) + # Pad val with None in case optional args are missing + val += (None, None, None, None) + base_cls_name, dflt_ext, reg_spec, flags, sort_pri, \ + read_code, write_code, read_predicate, write_predicate = val[:9] + # 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. @@ -1822,16 +2266,19 @@ StaticInstPtr (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 = {} attrList = ['reg_spec', 'flags', 'sort_pri', - 'read_code', 'write_code'] + 'read_code', 'write_code', + 'read_predicate', 'write_predicate'] if dflt_ext: dflt_ctype = self.operandTypeMap[dflt_ext] attrList.extend(['dflt_ctype', 'dflt_ext']) for attr in attrList: 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 @@ -1883,28 +2330,21 @@ StaticInstPtr else: return s - def update_if_needed(self, file, contents): - '''Update the output file only if the new contents are - different from the current contents. Minimizes the files that - need to be rebuilt after minor changes.''' - - file = os.path.join(self.output_dir, file) - update = False - if os.access(file, os.R_OK): - f = open(file, 'r') - old_contents = f.read() - f.close() - if contents != old_contents: - os.remove(file) # in case it's write-protected - update = True - else: - print 'File', file, 'is unchanged' - else: - update = True - if update: - f = open(file, 'w') - f.write(contents) - f.close() + def open(self, name, bare=False): + '''Open the output file for writing and include scary warning.''' + filename = os.path.join(self.output_dir, name) + f = open(filename, 'w') + if f: + if not bare: + f.write(ISAParser.scaremonger_template % self) + return f + + def update(self, file, contents): + '''Update the output file only. Scons should handle the case when + the new contents are unchanged using its built-in hash feature.''' + f = self.open(file) + f.write(contents) + f.close() # This regular expression matches '##include' directives includeRE = re.compile(r'^\s*##include\s+"(?P[^"]*)".*$', @@ -1932,7 +2372,7 @@ StaticInstPtr except IOError: error('Error including file "%s"' % filename) - self.fileNameStack.push((filename, 0)) + self.fileNameStack.push(LineTracker(filename)) # Find any includes and include them def replace(matchobj): @@ -1942,67 +2382,48 @@ StaticInstPtr self.fileNameStack.pop() return contents + AlreadyGenerated = {} + def _parse_isa_desc(self, isa_desc_file): '''Read in and parse the ISA description.''' + # The build system can end up running the ISA parser twice: once to + # finalize the build dependencies, and then to actually generate + # the files it expects (in src/arch/$ARCH/generated). This code + # doesn't do anything different either time, however; the SCons + # invocations just expect different things. Since this code runs + # within SCons, we can just remember that we've already run and + # not perform a completely unnecessary run, since the ISA parser's + # effect is idempotent. + if isa_desc_file in ISAParser.AlreadyGenerated: + return + + # grab the last three path components of isa_desc_file + self.filename = '/'.join(isa_desc_file.split('/')[-3:]) + # Read file and (recursively) all included files into a string. # PLY requires that the input be in a single string so we have to # do this up front. isa_desc = self.read_and_flatten(isa_desc_file) - # Initialize filename stack with outer file. - self.fileNameStack.push((isa_desc_file, 0)) - - # Parse it. - (isa_name, namespace, global_code, namespace_code) = \ - self.parse_string(isa_desc) - - # grab the last three path components of isa_desc_file to put in - # the output - filename = '/'.join(isa_desc_file.split('/')[-3:]) - - # generate decoder.hh - includes = '#include "base/bitfield.hh" // for bitfield support' - global_output = global_code.header_output - namespace_output = namespace_code.header_output - decode_function = '' - self.update_if_needed('decoder.hh', file_template % vars()) - - # generate decoder.cc - includes = '#include "decoder.hh"' - global_output = global_code.decoder_output - namespace_output = namespace_code.decoder_output - # namespace_output += namespace_code.decode_block - decode_function = namespace_code.decode_block - self.update_if_needed('decoder.cc', file_template % vars()) - - # generate per-cpu exec files - for cpu in self.cpuModels: - includes = '#include "decoder.hh"\n' - includes += cpu.includes - global_output = global_code.exec_output[cpu.name] - namespace_output = namespace_code.exec_output[cpu.name] - decode_function = '' - self.update_if_needed(cpu.filename, file_template % vars()) - - # The variable names here are hacky, but this will creat local - # variables which will be referenced in vars() which have the - # value of the globals. - MaxInstSrcRegs = self.maxInstSrcRegs - MaxInstDestRegs = self.maxInstDestRegs - # max_inst_regs.hh - self.update_if_needed('max_inst_regs.hh', - max_inst_regs_template % vars()) + # Initialize lineno tracker + self.lex.lineno = LineTracker(isa_desc_file) + + # Parse. + self.parse_string(isa_desc) + + ISAParser.AlreadyGenerated[isa_desc_file] = None def parse_isa_desc(self, *args, **kwargs): try: self._parse_isa_desc(*args, **kwargs) except ISAParserError, e: - e.exit(self.fileNameStack) + print backtrace(self.fileNameStack) + print "At %s:" % e.lineno + print e + sys.exit(1) # Called as script: get args from command line. -# Args are: +# Args are: if __name__ == '__main__': - execfile(sys.argv[1]) # read in CpuModel definitions - cpu_models = [CpuModel.dict[cpu] for cpu in sys.argv[4:]] - ISAParser(sys.argv[3], cpu_models).parse_isa_desc(sys.argv[2]) + ISAParser(sys.argv[2]).parse_isa_desc(sys.argv[1])