/* * Copyright (c) 2009-2011, IETR/INSA of Rennes * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the IETR/INSA of Rennes nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ package net.sf.orcc.ir.util; import static java.math.BigInteger.ONE; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; import java.util.List; import java.util.Map; import net.sf.orcc.OrccRuntimeException; import net.sf.orcc.df.Action; import net.sf.orcc.df.Actor; import net.sf.orcc.df.Pattern; import net.sf.orcc.df.Port; import net.sf.orcc.df.State; import net.sf.orcc.df.Transition; import net.sf.orcc.graph.Edge; import net.sf.orcc.ir.Arg; import net.sf.orcc.ir.ArgByVal; import net.sf.orcc.ir.Block; import net.sf.orcc.ir.BlockBasic; import net.sf.orcc.ir.BlockIf; import net.sf.orcc.ir.BlockWhile; import net.sf.orcc.ir.ExprString; import net.sf.orcc.ir.Expression; import net.sf.orcc.ir.InstAssign; import net.sf.orcc.ir.InstCall; import net.sf.orcc.ir.InstLoad; import net.sf.orcc.ir.InstPhi; import net.sf.orcc.ir.InstReturn; import net.sf.orcc.ir.InstStore; import net.sf.orcc.ir.Instruction; import net.sf.orcc.ir.Param; import net.sf.orcc.ir.Procedure; import net.sf.orcc.ir.Type; import net.sf.orcc.ir.TypeList; import net.sf.orcc.ir.Var; import net.sf.orcc.util.OrccLogger; import net.sf.orcc.util.OrccUtil; import net.sf.orcc.util.util.EcoreHelper; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.util.EcoreUtil; /** * This class defines an interpreter for an actor. The interpreter can * {@link #initialize()} and {@link #schedule()} the actor. * * @author Pierre-Laurent Lagalaye * @author Matthieu Wipliez * */ public class ActorInterpreter extends IrSwitch<Object> { /** the associated interpreted actor */ protected Actor actor; /** branch being visited */ protected int branch; /** the expression evaluator */ protected ExpressionEvaluator exprInterpreter; /** Actor's FSM current state */ protected State fsmState; /** * Creates a new interpreter without any associated {@link Actor} */ public ActorInterpreter() { this.exprInterpreter = new ExpressionEvaluator(); } /** * Creates a new interpreter. * * @param actor * the actor to interpret */ public ActorInterpreter(Actor actor) { this(); setActor(actor); } /** * Allocates the variables of the given pattern. * * @param pattern * a pattern */ final protected void allocatePattern(Pattern pattern) { for (Port port : pattern.getPorts()) { Var var = pattern.getVariable(port); if (var != null) { Object value = ValueUtil.createArray((TypeList) var.getType()); var.setValue(value); } } } /** * Calls the given native procedure. Does nothing by default. This method * may be overridden if one wishes to call native procedures. * * @param procedure * a native procedure * @param arguments * the arguments used to call the given procedure * @return the result of calling the given procedure */ protected Object callNativeProcedure(Procedure procedure, List<Arg> arguments) { return null; } /** * Calls the print procedure. Prints to stdout by default. This method may * be overridden. * * @param arguments * arguments of the print */ protected void callPrintProcedure(List<Arg> arguments) { for (Arg arg : arguments) { if (arg.isByVal()) { Expression expr = ((ArgByVal) arg).getValue(); if (expr.isExprString()) { // String characters rework for escaped control // management String str = ((ExprString) expr).getValue(); String unescaped = OrccUtil.getUnescapedString(str); OrccLogger.noticeRaw(unescaped); } else { Object value = exprInterpreter.doSwitch(expr); OrccLogger.noticeRaw(String.valueOf(value)); } } } } @Override public Object caseBlockBasic(BlockBasic block) { List<Instruction> instructions = block.getInstructions(); for (Instruction instruction : instructions) { Object result = doSwitch(instruction); if (result != null) { return result; } } return null; } @Override public Object caseBlockIf(BlockIf block) { // Interpret first expression ("if" condition) Object condition = exprInterpreter.doSwitch(block.getCondition()); Object ret; // if (condition is true) if (ValueUtil.isBool(condition)) { int oldBranch = branch; if (ValueUtil.isTrue(condition)) { doSwitch(block.getThenBlocks()); branch = 0; } else { doSwitch(block.getElseBlocks()); branch = 1; } ret = doSwitch(block.getJoinBlock()); branch = oldBranch; } else { throw new OrccRuntimeException("Condition " + new ExpressionPrinter().doSwitch(block.getCondition()) + " not boolean at line " + block.getLineNumber()); } return ret; } @Override public Object caseBlockWhile(BlockWhile block) { int oldBranch = branch; branch = 0; doSwitch(block.getJoinBlock()); // Interpret first expression ("while" condition) Object condition = exprInterpreter.doSwitch(block.getCondition()); // while (condition is true) do branch = 1; while (ValueUtil.isTrue(condition)) { doSwitch(block.getBlocks()); doSwitch(block.getJoinBlock()); // Interpret next value of "while" condition condition = exprInterpreter.doSwitch(block.getCondition()); } branch = oldBranch; return null; } @Override public Object caseInstAssign(InstAssign instr) { Var target = instr.getTarget().getVariable(); Object value = exprInterpreter.doSwitch(instr.getValue()); value = clipValue(target.getType(), value, instr); try { target.setValue(value); } catch (OrccRuntimeException e) { String file = actor.getFileName(); throw new OrccRuntimeException(file, instr.getLineNumber(), "", e); } return null; } @Override public Object caseInstCall(InstCall call) { // Get called procedure Procedure proc = call.getProcedure(); // Set the input parameters of the called procedure if any List<Arg> callParams = call.getArguments(); // Special "print" case if (call.isPrint()) { callPrintProcedure(callParams); } else if (proc.isNative()) { Object result = callNativeProcedure(proc, callParams); if (call.hasResult()) { call.getTarget().getVariable().setValue(result); } } else { List<Param> procParams = proc.getParameters(); for (int i = 0; i < callParams.size(); i++) { Var procVar = procParams.get(i).getVariable(); Arg arg = callParams.get(i); if (arg.isByVal()) { Expression value = ((ArgByVal) arg).getValue(); procVar.setValue(exprInterpreter.doSwitch(value)); } } // Interpret procedure body Object result = doSwitch(proc); if (call.hasResult()) { call.getTarget().getVariable().setValue(result); } } return null; } @Override public Object caseInstLoad(InstLoad instr) { Var target = instr.getTarget().getVariable(); Var source = instr.getSource().getVariable(); if (instr.getIndexes().isEmpty()) { target.setValue(source.getValue()); } else { Object array = source.getValue(); Object[] indexes = new Object[instr.getIndexes().size()]; int i = 0; for (Expression index : instr.getIndexes()) { indexes[i++] = exprInterpreter.doSwitch(index); } Type type = ((TypeList) source.getType()).getInnermostType(); try { Object value = ValueUtil.get(type, array, indexes); target.setValue(value); } catch (IndexOutOfBoundsException e) { throw new OrccRuntimeException( "Array Index Out of Bound at line " + instr.getLineNumber()); } } return null; } @Override public Object caseInstPhi(InstPhi phi) { Expression value = phi.getValues().get(branch); phi.getTarget().getVariable().setValue(exprInterpreter.doSwitch(value)); return null; } @Override public Object caseInstReturn(InstReturn instr) { if (instr.getValue() == null) { return null; } return exprInterpreter.doSwitch(instr.getValue()); } @Override public Object caseInstStore(InstStore instr) { Var target = instr.getTarget().getVariable(); Object value = exprInterpreter.doSwitch(instr.getValue()); if (instr.getIndexes().isEmpty()) { value = clipValue(target.getType(), value, instr); target.setValue(value); } else { Object array = target.getValue(); Object[] indexes = new Object[instr.getIndexes().size()]; int i = 0; for (Expression index : instr.getIndexes()) { indexes[i++] = exprInterpreter.doSwitch(index); } Type type = ((TypeList) target.getType()).getInnermostType(); value = clipValue(type, value, instr); try { ValueUtil.set(type, array, value, indexes); } catch (IndexOutOfBoundsException e) { throw new OrccRuntimeException( "Array Index Out of Bound at line " + instr.getLineNumber() + ""); } } return null; } @Override public Object caseProcedure(Procedure procedure) { // Allocate local List variables for (Var local : procedure.getLocals()) { Type type = local.getType(); if (type.isList()) { Object value = ValueUtil .createArray((TypeList) local.getType()); local.setValue(value); } } return doSwitch(procedure.getBlocks()); } /** * Returns true if the action has no output pattern, or if it has an output * pattern and there is enough room in the FIFOs to satisfy it. * * @param outputPattern * output pattern of an action * @return true if the pattern is empty or satisfiable */ protected boolean checkOutputPattern(Pattern outputPattern) { return true; } /** * Returns a new value clipped to: * <ul> * <li>[-2<sup>n-1</sup>; 2<sup>n-1</sup> - 1] if type is int (size=n)</li> * <li>[0; 2<sup>n-1</sup>] if type is uint (size=n)</li> * </ul> * Prints a warning in case of signed overflow/underflow. * * @param type * type of the target variable * @param value * a value * @param instruction * the instruction * @return the original value or a new value */ protected Object clipValue(Type type, Object value, Instruction instruction) { if (!ValueUtil.isInt(value)) { return value; } BigInteger intVal = (BigInteger) value; // converts to unsigned n-bit number int n = type.getSizeInBits(); BigInteger twoPowSize = ONE.shiftLeft(n); BigInteger clippedValue = intVal.and(twoPowSize.subtract(ONE)); // check signed overflow/underflow if (type.isInt()) { // if MSB is set, subtract 2**n to make negative number if (clippedValue.testBit(n - 1)) { clippedValue = clippedValue.subtract(twoPowSize); } if (!clippedValue.equals(intVal)) { String container = ""; Action parentAction = EcoreHelper.getContainerOfType( instruction, Action.class); if (parentAction != null) { container = parentAction.getName(); } else if (EcoreHelper.getContainerOfType(instruction, Procedure.class) != null) { container = EcoreHelper.getContainerOfType(instruction, Procedure.class).getName(); } OrccLogger.debugln("[signed overflow/underflow] " + actor.getName() + ":" + container + " line: " + instruction.getLineNumber()); } } return clippedValue; } /** * Visits the blocks of the given block list. * * @param blocks * a list of blocks that belong to a procedure */ protected Object doSwitch(List<Block> blocks) { for (Block block : blocks) { Object result = doSwitch(block); if (result != null) { return result; } } return null; } /** * Executes the given action. This implementation allocates input/output * pattern and executes the body. Should be overriden by implementations to * perform read/write from/to FIFOs. * * @param action * an action */ protected void execute(Action action) { Pattern input = action.getInputPattern(); Pattern output = action.getOutputPattern(); allocatePattern(input); allocatePattern(output); doSwitch(action.getBody()); } /** * Returns the value of the <code>actor</code> attribute. This may be * <code>null</code> if the visitor did not set it. * * @return the value of the <code>actor</code> attribute */ final public Actor getActor() { return actor; } /** * Returns the current FSM state. * * @return the current FSM state */ public final State getFsmState() { return fsmState; } /** * Get the next schedulable action to be executed for this actor * * @return the schedulable action or null */ public Action getNextAction() { // Check next schedulable action in respect of the priority order for (Action action : actor.getActionsOutsideFsm()) { if (isSchedulable(action)) { if (checkOutputPattern(action.getOutputPattern())) { return action; } } } if (actor.hasFsm()) { // Then check for next FSM transition for (Edge edge : fsmState.getOutgoing()) { Transition transition = (Transition) edge; Action action = transition.getAction(); if (isSchedulable(action)) { // Update FSM state if (checkOutputPattern(action.getOutputPattern())) { fsmState = transition.getTarget(); return action; } return null; } } } return null; } /** * Initializes external resources referenced by an object (actor, procedure, * etc.) * * @param obj * an EObject which potentially use external variables or * procedures */ private void initExternalResources(EObject obj) { Map<EObject, Collection<Setting>> map = EcoreUtil.ExternalCrossReferencer .find(obj); for (EObject externalObject : map.keySet()) { if (externalObject instanceof Var && ((Var) externalObject).getValue() == null) { initializeVar((Var) externalObject); } else if (externalObject instanceof Procedure) { initExternalResources(externalObject); } } } /** * Initialize interpreted actor. That is to say constant parameters, * initialized state variables, allocation and initialization of state * arrays. */ public void initialize() { try { // initializes parameters for (Var var : actor.getParameters()) { initializeVar(var); } // initializes state variables for (Var stateVar : actor.getStateVars()) { initializeVar(stateVar); } // initializes runtime value of constants declared in units initExternalResources(actor); // initializes FSM status (if any) if (actor.hasFsm()) { fsmState = actor.getFsm().getInitialState(); } else { fsmState = null; } // Get initializing procedure if any for (Action action : actor.getInitializes()) { if (isSchedulable(action)) { execute(action); break; } } } catch (OrccRuntimeException ex) { throw new OrccRuntimeException("Runtime exception thrown by actor " + actor.getName(), ex); } } /** * Initializes the given variable. * * @param variable * a variable */ protected void initializeVar(Var variable) { Type type = variable.getType(); Expression initConst = variable.getInitialValue(); if (initConst == null) { Object value; if (type.isBool()) { value = false; } else if (type.isFloat()) { value = BigDecimal.ZERO; } else if (type.isInt() || type.isUint()) { value = BigInteger.ZERO; } else if (type.isList()) { value = ValueUtil.createArray((TypeList) type); } else if (type.isString()) { value = ""; } else { value = null; } variable.setValue(value); } else { // evaluate initial constant value if (type.isList()) { exprInterpreter.setType((TypeList) type); } variable.setValue(exprInterpreter.doSwitch(initConst)); } } /** * Returns true if the given action is schedulable. This implementation * allocates the peek pattern and calls the scheduler procedure. This method * should be overridden to define how to test the schedulability of an * action. * * @param action * an action * @return true if the given action is schedulable */ protected boolean isSchedulable(Action action) { Pattern pattern = action.getPeekPattern(); allocatePattern(pattern); Object result = doSwitch(action.getScheduler()); return ValueUtil.isTrue(result); } /** * Schedule next schedulable action if any * * @return <code>true</code> if an action was scheduled, <code>false</code> * otherwise */ public boolean schedule() { try { // "Synchronous-like" scheduling policy : schedule only 1 action per // actor at each "schedule" (network logical cycle) call Action action = getNextAction(); if (action == null) { return false; } else { execute(action); return true; } } catch (OrccRuntimeException ex) { throw new OrccRuntimeException("Runtime exception thrown by actor " + actor.getName(), ex); } } /** * Sets the actor interpreter by this interpreter. * * @param actor * an actor */ protected void setActor(Actor actor) { this.actor = actor; } }