# -*- coding: utf-8 -*- # # AWL simulator - CPU call stack # # Copyright 2012-2018 Michael Buesch # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # from __future__ import division, absolute_import, print_function, unicode_literals #from awlsim.common.cython_support cimport * #@cy from awlsim.common.compat import * from awlsim.common.exceptions import * from awlsim.core.datatypes import * from awlsim.core.memory import * #+cimport from awlsim.core.offset import * #+cimport from awlsim.core.operatortypes import * #+cimport from awlsim.core.operators import * #+cimport from awlsim.core.blocks import * #+cimport from awlsim.core.blockinterface import * from awlsim.core.datablocks import * #+cimport from awlsim.core.parameters import * #+cimport from awlsim.core.parenstack import * #+cimport from awlsim.core.util import * __all__ = [ "CallStackElem", "make_CallStackElem", ] class CallStackElem(object): #+cdef """Call stack element. """ __slots__ = ( "prevCse", # Previous call stack element (None if at OB level) "cpu", # S7CPU that belongs to this CSE "parenStack", # Active parenthesis stack "insns", # Instruction list that is being executed "nrInsns", # Length of the instruction list "ip", # Current instruction pointer "block", # CodeBlock that is being executed "isRawCall", # True, if this call is raw (UC, CC) "instanceDB", # Instance data block, if any "prevDbRegister", # DB register from before this call "prevDiRegister", # DI register from before this call "prevAR2value", # AR2 register from before this call "_outboundParams", # List of outbound AwlParamAssign. (Internal) "_interfRefs", # List of translated interface references (Internal; FC only) ) # Get an FC interface operand by interface field index. def getInterfIdxOper(self, interfaceFieldIndex): #@nocy #@cy cpdef AwlOperator getInterfIdxOper(self, uint32_t interfaceFieldIndex): if self._interfRefs is None: #+unlikely # Huh, no interface ref? We might have been called via raw call. raise AwlSimError("The block interface field could not " "be found. This probably means that this function block " "has been called with a raw call instruction like UC or CC, " "but the function block has an interface. This is not " "allowed in Awlsim.") return self._interfRefs[interfaceFieldIndex] # FB parameter translation: # Translate FB DB-pointer variable. # This is used for FB IN_OUT compound data type parameters. # Returns the actual DB-pointer data. (Not an operator!) def _FB_trans_dbpointer(self, param, rvalueOp): #@nocy #@cy cdef uint64_t _FB_trans_dbpointer(self, AwlParamAssign param, AwlOperator rvalueOp): #@cy cdef uint64_t dbPtr #@cy cdef int32_t dbNumber dbPtr = 0 dbNumber = rvalueOp.offset.dbNumber if dbNumber >= 0: dbPtr = dbNumber dbPtr <<= 32 dbPtr |= rvalueOp.makePointerValue() return dbPtr # FC parameter translation: # Don't perform translation. # For various MEM and BLKREF accesses. # Returns the translated rvalueOp. def _FC_trans_direct(self, param, rvalueOp): #@nocy #@cy cdef AwlOperator _FC_trans_direct(self, AwlParamAssign param, AwlOperator rvalueOp): return rvalueOp # FC parameter translation: # Copy parameter r-value to the caller-L-stack, if inbound # and register a copy-back request, if outbound. # Returns the translated rvalueOp. def _FC_trans_copyToVL(self, param, rvalueOp): #@nocy #@cy cdef AwlOperator _FC_trans_copyToVL(self, AwlParamAssign param, AwlOperator rvalueOp): #@cy cdef S7CPU cpu #@cy cdef AwlOffset loffset #@cy cdef AwlOperator oper #@cy cdef uint32_t widthMaskAll widthMaskAll = AwlOperatorWidths.WIDTH_MASK_ALL cpu = self.cpu # Allocate space in the caller-L-stack. loffset = cpu.activeLStack.alloc(rvalueOp.width) # Make an operator for the allocated space. oper = make_AwlOperator(AwlOperatorTypes.MEM_L, rvalueOp.width, loffset, rvalueOp.insn) # Write the value to the allocated space. # This would only be necessary for inbound parameters, # but S7 supports read access to certain outbound # FC parameters as well. So copy the value unconditionally. cpu.store(oper, cpu.fetch(rvalueOp, widthMaskAll), widthMaskAll) # Change the operator to VL oper.operType = AwlOperatorTypes.MEM_VL # If outbound, save param and operator for return from CALL. # Do not do this for immediates (which would be pointer # immediates, for example), because there is nothing to copy # back in that case. if param.isOutbound and not rvalueOp.isImmediate(): param.scratchSpaceOp = oper self._outboundParams.append(param) return oper # FC parameter translation: # Create a DB-pointer to the r-value in the caller's L-stack (VL). # Returns the translated rvalueOp. def _FC_trans_dbpointerInVL(self, param, rvalueOp): #@nocy #@cy cdef AwlOperator _FC_trans_dbpointerInVL(self, AwlParamAssign param, AwlOperator rvalueOp): #@cy cdef S7CPU cpu #@cy cdef AwlOffset loffset #@cy cdef int32_t dbNumber #@cy cdef int64_t area #@cy cdef uint64_t area_u64 #@cy cdef AwlOperator storeOper #@cy cdef uint32_t widthMaskAll widthMaskAll = AwlOperatorWidths.WIDTH_MASK_ALL cpu = self.cpu # Allocate space for the DB-ptr in the caller-L-stack loffset = cpu.activeLStack.alloc(48) # 48 bits # Create and store the the DB-ptr to the allocated space. storeOper = make_AwlOperator(AwlOperatorTypes.MEM_L, 16, loffset, rvalueOp.insn) if rvalueOp.operType == AwlOperatorTypes.MEM_DI: dbNumber = cpu.diRegister.index else: dbNumber = rvalueOp.offset.dbNumber cpu.store(storeOper, make_AwlMemoryObject_fromScalar16(max(0, dbNumber)), widthMaskAll) storeOper.offset = loffset.addInt(2, 0) storeOper.width = 32 area = AwlIndirectOpConst.optype2area(rvalueOp.operType) if area < 0: #+unlikely raise AwlSimBug("FC_trans_dbpointerInVL: Invalid rValueOp area. " "(area=%d, operType=%d)" % (area, rvalueOp.operType)) area_u64 = area if area_u64 == PointerConst.AREA_L_S: area_u64 = PointerConst.AREA_VL_S elif area_u64 == PointerConst.AREA_VL_S: #+unlikely raise AwlSimError("Cannot forward VL-parameter " "to called FC") elif area_u64 == PointerConst.AREA_DI_S: area_u64 = PointerConst.AREA_DB_S cpu.store(storeOper, make_AwlMemoryObject_fromScalar32(area_u64 | rvalueOp.offset.toPointerValue()), widthMaskAll) # Return the operator for the DB pointer. return make_AwlOperator(AwlOperatorTypes.MEM_VL, 48, loffset, rvalueOp.insn) # FC parameter translation: # Copy the r-value to the caller's L-stack (VL) and also create # a DB-pointer to the copied value in VL. # Returns the translated rvalueOp. def _FC_trans_copyToVLWithDBPtr(self, param, rvalueOp): #@nocy #@cy cdef AwlOperator _FC_trans_copyToVLWithDBPtr(self, AwlParamAssign param, AwlOperator rvalueOp): #@cy cdef AwlOperator oper oper = self._FC_trans_copyToVL(param, rvalueOp) oper.operType = AwlOperatorTypes.MEM_L return self._FC_trans_dbpointerInVL(param, oper) # FC parameter translation: # Translate L-stack access r-value. # Returns the translated rvalueOp. def _FC_trans_MEM_L(self, param, rvalueOp): #@nocy #@cy cdef AwlOperator _FC_trans_MEM_L(self, AwlParamAssign param, AwlOperator rvalueOp): # r-value is an L-stack memory access. if rvalueOp.compound: # rvalue is a compound data type. # Create a DB-pointer to it in VL. return self._FC_trans_dbpointerInVL(param, rvalueOp) # Translate it to a VL-stack memory access. return make_AwlOperator(AwlOperatorTypes.MEM_VL, rvalueOp.width, rvalueOp.offset, rvalueOp.insn) # FC parameter translation: # Translate DB access r-value. # Returns the translated rvalueOp. def _FC_trans_MEM_DB(self, param, rvalueOp, copyToVL=False): #@nocy #@cy cdef AwlOperator _FC_trans_MEM_DB(self, AwlParamAssign param, AwlOperator rvalueOp, _Bool copyToVL=False): #@cy cdef AwlOffset offset #@cy cdef int32_t dbNumber # A (fully qualified) DB variable is passed to an FC. dbNumber = rvalueOp.offset.dbNumber if dbNumber >= 0: # This is a fully qualified DB access. if rvalueOp.compound: # rvalue is a compound data type. # Create a DB-pointer to it in VL. return self._FC_trans_dbpointerInVL(param, rvalueOp) # Basic data type. self.cpu.openDB(dbNumber, False) copyToVL = True if copyToVL: # Copy to caller-L-stack. return self._FC_trans_copyToVL(param, rvalueOp) # Do not copy to caller-L-stack. Just make a DB-reference. offset = rvalueOp.offset.dup() return make_AwlOperator(AwlOperatorTypes.MEM_DB, rvalueOp.width, offset, rvalueOp.insn) # FC parameter translation: # Translate DI access r-value. # Returns the translated rvalueOp. def _FC_trans_MEM_DI(self, param, rvalueOp): #@nocy #@cy cdef AwlOperator _FC_trans_MEM_DI(self, AwlParamAssign param, AwlOperator rvalueOp): # A parameter is forwarded from an FB to an FC if rvalueOp.compound: # rvalue is a compound data type. # Create a DB-pointer to it in VL. return self._FC_trans_dbpointerInVL(param, rvalueOp) # Basic data type. # Copy the value to VL. return self._FC_trans_copyToVL(param, rvalueOp) # FC parameter translation: # Translate named local variable r-value. # Returns the translated rvalueOp. def _FC_trans_NAMED_LOCAL(self, param, rvalueOp): #@nocy #@cy cdef AwlOperator _FC_trans_NAMED_LOCAL(self, AwlParamAssign param, AwlOperator rvalueOp): #@cy cdef AwlOperator oper # r-value is a named-local (#abc) oper = self.cpu.callStackTop.getInterfIdxOper(rvalueOp.interfaceIndex) if oper.operType == AwlOperatorTypes.MEM_DB: return self._FC_trans_MEM_DB(param, oper, True) # Call the operator translation handler (Python) try: #@nocy trans = self._FC_paramTrans[oper.operType] #@nocy except KeyError as e: #@nocy #@nocov self._FCTransBug(param, oper) #@nocy return trans(self, param, oper) #@nocy # Call the operator translation handler (Cython) #@cy oper = self._translateFCParam(param, oper) #@cy if oper is None: #@cy self._FCTransBug(param, oper) #@cy return oper # FC call parameter translators _FC_paramTrans = { #@nocy AwlOperatorTypes.IMM : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.IMM_REAL : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.IMM_S5T : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.IMM_TIME : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.IMM_DATE : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.IMM_TOD : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.IMM_DT : _FC_trans_copyToVLWithDBPtr, #@nocy AwlOperatorTypes.IMM_PTR : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.IMM_STR : _FC_trans_copyToVLWithDBPtr, #@nocy AwlOperatorTypes.MEM_E : _FC_trans_direct, #@nocy AwlOperatorTypes.MEM_A : _FC_trans_direct, #@nocy AwlOperatorTypes.MEM_M : _FC_trans_direct, #@nocy AwlOperatorTypes.MEM_L : _FC_trans_MEM_L, #@nocy AwlOperatorTypes.MEM_VL : _FC_trans_copyToVL, #@nocy AwlOperatorTypes.MEM_DB : _FC_trans_MEM_DB, #@nocy AwlOperatorTypes.MEM_DI : _FC_trans_MEM_DI, #@nocy AwlOperatorTypes.MEM_T : _FC_trans_direct, #@nocy AwlOperatorTypes.MEM_Z : _FC_trans_direct, #@nocy AwlOperatorTypes.MEM_PA : _FC_trans_direct, #@nocy AwlOperatorTypes.MEM_PE : _FC_trans_direct, #@nocy AwlOperatorTypes.BLKREF_FC : _FC_trans_direct, #@nocy AwlOperatorTypes.BLKREF_FB : _FC_trans_direct, #@nocy AwlOperatorTypes.BLKREF_DB : _FC_trans_direct, #@nocy AwlOperatorTypes.NAMED_LOCAL : _FC_trans_NAMED_LOCAL, #@nocy } #@nocy #@cy cdef AwlOperator _translateFCParam(self, AwlParamAssign param, AwlOperator rvalueOp): #@cy cdef uint32_t operType #@cy cdef AwlOperator oper #@cy #@cy operType = rvalueOp.operType #@cy if operType == AwlOperatorTypes.IMM: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_REAL: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_S5T: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_TIME: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_DATE: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_TOD: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_DT: #@cy oper = self._FC_trans_copyToVLWithDBPtr(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_PTR: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.IMM_STR: #@cy oper = self._FC_trans_copyToVLWithDBPtr(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_E: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_A: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_M: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_L: #@cy oper = self._FC_trans_MEM_L(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_VL: #@cy oper = self._FC_trans_copyToVL(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_DB: #@cy oper = self._FC_trans_MEM_DB(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_DI: #@cy oper = self._FC_trans_MEM_DI(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_T: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_Z: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_PA: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.MEM_PE: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.BLKREF_FC: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.BLKREF_FB: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.BLKREF_DB: #@cy oper = self._FC_trans_direct(param, rvalueOp) #@cy elif operType == AwlOperatorTypes.NAMED_LOCAL: #@cy oper = self._FC_trans_NAMED_LOCAL(param, rvalueOp) #@cy else: #@cy oper = None #@cy return oper def _FCTransError(self, param, oper): #@nocov raise AwlSimError("Do not know how to translate " "FC parameter '%s' for call. The specified " "actual-parameter is not allowed in this call." % ( str(param))) def _FCTransBug(self, param, oper): raise AwlSimBug("Unhandled call translation of " "named local parameter assignment:\n" "'%s' => r-value operator '%s'" % ( (str(param), str(oper)))) # Handle the exit from this code block. # This stack element (self) will already have been # removed from the CPU's call stack. def handleBlockExit(self): #+cdef #@cy cdef S7CPU cpu #@cy cdef AwlOffset instanceBaseOffset #@cy cdef AwlParamAssign param #@cy cdef uint32_t widthMaskAll cpu = self.cpu # Destroy this call stack element. cpu.activeLStack.exitStackFrame() if not self.isRawCall: widthMaskAll = AwlOperatorWidths.WIDTH_MASK_ALL # Handle outbound parameters. if self.block.isFB: # We are returning from an FB. # Get the multi-instance base offset. instanceBaseOffset = make_AwlOffset_fromPointerValue(cpu.ar2.get()) # Restore the AR2 register. cpu.ar2.set(self.prevAR2value) # Transfer data out of DBI. structInstance = cpu.diRegister.structInstance for param in self._outboundParams: cpu.store( param.rvalueOp, structInstance.getFieldData(param.lValueStructField, instanceBaseOffset), widthMaskAll ) # Assign the DB/DI registers. cpu.dbRegister, cpu.diRegister = self.instanceDB, self.prevDiRegister else: # We are returning from an FC. # Restore the AR2 register. cpu.ar2.set(self.prevAR2value) # Transfer data out of temporary sections. for param in self._outboundParams: cpu.store( param.rvalueOp, cpu.fetch(make_AwlOperator(AwlOperatorTypes.MEM_L, param.scratchSpaceOp.width, param.scratchSpaceOp.offset, None), widthMaskAll), widthMaskAll ) # Assign the DB/DI registers. cpu.dbRegister, cpu.diRegister = self.prevDbRegister, self.prevDiRegister # Unlink this call stack element from the previous one. self.prevCse = None def __repr__(self): #@nocov return "CallStackElem of %s" % str(self.block) # # make_CallStackElem() - Create a CallStackElem instance. # # Init the call stack element. # cpu -> The CPU this runs on. # block -> The code block that is being called. # instanceDB -> The instance-DB, if FB-call. Otherwise None. # instanceBaseOffset -> AwlOffset for use as AR2 instance base (multi-instance). # If None, AR2 is not modified. # parameters -> A tuple of AwlParamAssign instances # representing the parameter assignments in CALL insn. # isRawCall -> True, if the calling instruction was UC or CC. # def make_CallStackElem(cpu, #@nocy block, #@nocy instanceDB, #@nocy instanceBaseOffset, #@nocy parameters, #@nocy isRawCall, #@nocy CallStackElem=CallStackElem): #@nocy #cdef CallStackElem make_CallStackElem(S7CPU cpu, #@cy # CodeBlock block, #@cy # DB instanceDB, #@cy # AwlOffset instanceBaseOffset, #@cy # tuple parameters, #@cy # _Bool isRawCall): #@cy #@cy cdef CallStackElem cse #@cy cdef AwlOperator oper #@cy cdef AwlParamAssign param #@cy cdef AwlStructField structField #@cy cdef AwlStructInstance structInstance #@cy cdef uint32_t widthMaskAll #@cy cdef AwlMemoryObject memObj cse = CallStackElem() cse.cpu = cpu cse.parenStack = make_ParenStack(cpu) cse.ip = 0 cse.block = block cse.insns = block.insns cse.nrInsns = block.nrInsns cse.isRawCall = isRawCall cse.instanceDB = instanceDB cse.prevDbRegister = cpu.dbRegister cse.prevDiRegister = cpu.diRegister cse.prevCse = cpu.callStackTop # Handle parameters cse._outboundParams = [] if parameters and not isRawCall: #@nocy #@cy if not isRawCall: if block.isFB: structInstance = instanceDB.structInstance widthMaskAll = AwlOperatorWidths.WIDTH_MASK_ALL # This is a call to an FB. # Copy the inbound data into the instance DB # and add the outbound parameters to the list. for param in parameters: structField = param.lValueStructField if param.isOutbound: # This is an outbound parameter. # If it is not IN_OUT compound data type, # add it to the outbound parameter list # for use at BE time. if not param.isInbound or\ not structField.compound: cse._outboundParams.append(param) if param.isInbound: # This is an inbound parameter. # Check if this is an IN_OUT compound data # type variable. These are passed via DB-ptr. if param.isOutbound and\ structField.compound: # Compound data type with IN_OUT decl. # Make a DB-ptr to the actual data. memObj = make_AwlMemoryObject_fromScalar48( cse._FB_trans_dbpointer(param, param.rvalueOp)) # Get the DB-ptr struct field. structField = structField.finalOverride else: # Non-compound (basic) data type or # not IN_OUT declaration. # Get the actual data. if structField.callByRef: # Do not fetch. Type is passed 'by reference'. # This is for TIMER, COUNTER, etc... memObj = make_AwlMemoryObject_fromScalar16( param.rvalueOp.resolve(True).offset.byteOffset) else: memObj = cpu.fetch(param.rvalueOp, widthMaskAll) # Transfer data into DBI. structInstance.setFieldData(structField, memObj, instanceBaseOffset) cse._interfRefs = None else: # This is a call to an FC. # Prepare the interface (IN/OUT/INOUT) references. # cse._interfRefs is a dict of AwlOperators for the FC interface. # The key of cse._interfRefs is the interface field index. # This dict is used by the CPU for lookup and resolve of # the FC interface r-value. cse._interfRefs = {} for param in parameters: oper = param.rvalueOp # Call the operator translation handler (Python) try: #@nocy trans = cse._FC_paramTrans[oper.operType] #@nocy except KeyError as e: #@nocy #@nocov cse._FCTransError(param, oper) #@nocy cse._interfRefs[param.interfaceFieldIndex] = trans( #@nocy cse, param, oper) #@nocy # Call the operator translation handler (Cython) #@cy oper = cse._translateFCParam(param, oper) #@cy if oper is None: #@cy cse._FCTransError(param, oper) #@cy cse._interfRefs[param.interfaceFieldIndex] = oper else: cse._interfRefs = None # Prepare the localdata stack. cpu.activeLStack.enterStackFrame() if block.tempAllocation: cpu.activeLStack.alloc(block.tempAllocation * 8) # Set AR2 to the specified multi-instance base # and save the old AR2 value. cse.prevAR2value = cpu.ar2.get() if instanceBaseOffset is not None: cpu.ar2.set(PointerConst.AREA_DB_S |\ instanceBaseOffset.toPointerValue()) return cse