# -*- coding: utf-8 -*- # # AWL simulator - CPU call stack # # Copyright 2012-2014 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.compat import * from awlsim.core.datatypes import * from awlsim.core.blocks import * from awlsim.core.parameters import * from awlsim.core.objectcache import * from awlsim.core.lstack import * from awlsim.core.util import * class CallStackElem(object): "Call stack element" lallocCache = ObjectCache( lambda cpu: LStackAllocator(cpu.specs.nrLocalbytes) ) @classmethod def resetCache(cls): cls.lallocCache.reset() def __init__(self, cpu, block, instanceDB=None, instanceBaseOffset=None, parameters=(), isRawCall=False): # 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 -> An iterable of AwlParamAssign instances # representing the parameter assignments in CALL insn. # isRawCall -> True, if the calling instruction was UC or CC. self.cpu = cpu self.parenStack = [] self.ip = 0 self.block = block self.insns = block.insns self.isRawCall = isRawCall self.instanceDB = instanceDB self.prevDbRegister = cpu.dbRegister self.prevDiRegister = cpu.diRegister self.lalloc = self.lallocCache.get(cpu) self.lalloc.allocation = block.interface.tempAllocation self.localdata = self.lalloc.localdata # Set AR2 to the specified multi-instance base # and save the old AR2 value. self.prevAR2value = cpu.ar2.get() if instanceBaseOffset is not None: cpu.ar2.set(AwlIndirectOp.AREA_DB |\ instanceBaseOffset.toPointerValue()) # Handle parameters self.__outboundParams = [] if parameters and not isRawCall: if block.isFB: structInstance, callByRef_Types =\ instanceDB.structInstance, \ BlockInterface.callByRef_Types # 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: if param.isOutbound: # This is an outbound parameter. self.__outboundParams.append(param) if param.isInbound: # This is an inbound parameter. # Transfer data into DBI structField = param.lValueStructField if structField.dataType.type in callByRef_Types: data = param.rvalueOp.resolve().value.byteOffset else: data = cpu.fetch(param.rvalueOp) structInstance.setFieldData(structField, data, instanceBaseOffset) else: # This is a call to an FC. # Prepare the interface (IN/OUT/INOUT) references. # self.interfRefs is a dict of AwlOperators for the FC interface. # The key of self.interfRefs is the interface field index. # This dict is used by the CPU for lookup and resolve of # the FC interface r-value. self.interfRefs = {} for param in parameters: try: trans = self.__paramTrans[param.rvalueOp.type] except KeyError as e: 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)) self.interfRefs[param.interfaceFieldIndex] = trans(self, param, param.rvalueOp) # Don't perform translation. # For various MEM and BLKREF accesses. def __trans_direct(self, param, rvalueOp): return rvalueOp # Copy parameter r-value to the caller-L-stack, if inbound # and register a copy-back request, if outbound. def __trans_copyToVL(self, param, rvalueOp): # Allocate space in the caller-L-stack. lalloc = self.cpu.callStackTop.lalloc loffset = lalloc.alloc(rvalueOp.width) # Make an operator for the allocated space. oper = AwlOperator(AwlOperator.MEM_L, rvalueOp.width, loffset, rvalueOp.insn) if param.isInbound: # Write the value to the allocated space. self.cpu.store(oper, self.cpu.fetch(rvalueOp)) # Change the operator to VL oper.type = oper.MEM_VL # If outbound, save param and operator for return from CALL. if param.isOutbound: param.scratchSpaceOp = oper self.__outboundParams.append(param) return oper # Create a DB-pointer to the r-value in the caller's L-stack (VL). def __trans_dbpointerInVL(self, param, rvalueOp): # Allocate space for the DB-ptr in the caller-L-stack lalloc = self.cpu.callStackTop.lalloc loffset = lalloc.alloc(48) # 48 bits # Create and store the the DB-ptr to the allocated space. storeOper = AwlOperator(AwlOperator.MEM_L, 16, loffset) if rvalueOp.type == AwlOperator.MEM_DI: dbNumber = self.cpu.diRegister.index else: dbNumber = rvalueOp.value.dbNumber self.cpu.store(storeOper, 0 if dbNumber is None else dbNumber) storeOper.value = loffset + AwlOffset(2) storeOper.width = 32 area = AwlIndirectOp.optype2area[rvalueOp.type] if area == AwlIndirectOp.AREA_L: area = AwlIndirectOp.AREA_VL elif area == AwlIndirectOp.AREA_VL: raise AwlSimError("Cannot forward VL-parameter " "to called FC") elif area == AwlIndirectOp.AREA_DI: area = AwlIndirectOp.AREA_DB self.cpu.store(storeOper, area | rvalueOp.value.toPointerValue()) # Return the operator for the DB pointer. return AwlOperator(AwlOperator.MEM_VL, 48, loffset, rvalueOp.insn) # Translate L-stack access r-value. def __trans_MEM_L(self, param, 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.__trans_dbpointerInVL(param, rvalueOp) # Translate it to a VL-stack memory access. return AwlOperator(rvalueOp.MEM_VL, rvalueOp.width, rvalueOp.value, rvalueOp.insn) # Translate DB access r-value. def __trans_MEM_DB(self, param, rvalueOp, copyToVL=False): # A (fully qualified) DB variable is passed to an FC. if rvalueOp.value.dbNumber is not None: # 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.__trans_dbpointerInVL(param, rvalueOp) # Basic data type. self.cpu.run_AUF(AwlOperator(AwlOperator.BLKREF_DB, 16, AwlOffset(rvalueOp.value.dbNumber), rvalueOp.insn)) copyToVL = True if copyToVL: # Copy to caller-L-stack. return self.__trans_copyToVL(param, rvalueOp) # Do not copy to caller-L-stack. Just make a DB-reference. offset = rvalueOp.value.dup() return AwlOperator(rvalueOp.MEM_DB, rvalueOp.width, offset, rvalueOp.insn) # Translate DI access r-value. def __trans_MEM_DI(self, param, 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.__trans_dbpointerInVL(param, rvalueOp) # Basic data type. # Copy the value to VL. return self.__trans_copyToVL(param, rvalueOp) # Translate named local variable r-value. def __trans_NAMED_LOCAL(self, param, rvalueOp): # r-value is a named-local (#abc) oper = self.cpu.callStackTop.interfRefs[rvalueOp.interfaceIndex] if oper.type == oper.MEM_DB: return self.__trans_MEM_DB(param, oper, True) try: return self.__paramTrans[oper.type](self, param, oper) except KeyError as e: raise AwlSimBug("Unhandled call translation of " "named local parameter assignment:\n" "'%s' => r-value operator '%s'" %\ (str(param), str(oper))) # FC call parameter translators __paramTrans = { AwlOperator.IMM : __trans_copyToVL, AwlOperator.IMM_REAL : __trans_copyToVL, AwlOperator.IMM_S5T : __trans_copyToVL, AwlOperator.IMM_TIME : __trans_copyToVL, AwlOperator.IMM_DATE : __trans_copyToVL, AwlOperator.IMM_TOD : __trans_copyToVL, AwlOperator.IMM_DT : __trans_copyToVL, AwlOperator.IMM_PTR : __trans_copyToVL, AwlOperator.MEM_E : __trans_direct, AwlOperator.MEM_A : __trans_direct, AwlOperator.MEM_M : __trans_direct, AwlOperator.MEM_L : __trans_MEM_L, AwlOperator.MEM_VL : __trans_copyToVL, AwlOperator.MEM_DB : __trans_MEM_DB, AwlOperator.MEM_DI : __trans_MEM_DI, AwlOperator.MEM_T : __trans_direct, AwlOperator.MEM_Z : __trans_direct, AwlOperator.MEM_PA : __trans_direct, AwlOperator.MEM_PE : __trans_direct, AwlOperator.BLKREF_FC : __trans_direct, AwlOperator.BLKREF_FB : __trans_direct, AwlOperator.BLKREF_DB : __trans_direct, AwlOperator.NAMED_LOCAL : __trans_NAMED_LOCAL, } # 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): cpu = self.cpu if not self.isRawCall: # Handle outbound parameters. if self.block.isFB: # We are returning from an FB. # Get the multi-instance base offset. instanceBaseOffset = 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) ) # 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(AwlOperator(AwlOperator.MEM_L, param.scratchSpaceOp.width, param.scratchSpaceOp.value)) ) # Assign the DB/DI registers. cpu.dbRegister, cpu.diRegister = self.prevDbRegister, self.prevDiRegister # Destroy this call stack element (self). # Put the L-stack back into the cache, if the size did not change. if len(self.localdata) == cpu.specs.nrLocalbytes: self.lallocCache.put(self.lalloc) def __repr__(self): return str(self.block)