/*
* Copyright (c) 2011, Abo Akademi University
* 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 Abo Akademi University 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.backends.promela.transform;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.orcc.OrccRuntimeException;
import net.sf.orcc.df.Action;
import net.sf.orcc.df.Actor;
import net.sf.orcc.df.FSM;
import net.sf.orcc.df.Port;
import net.sf.orcc.df.State;
import net.sf.orcc.df.Transition;
import net.sf.orcc.df.util.DfVisitor;
import net.sf.orcc.graph.Edge;
import net.sf.orcc.ir.ExprVar;
import net.sf.orcc.ir.InstStore;
import net.sf.orcc.ir.Var;
import net.sf.orcc.ir.util.AbstractIrVisitor;
import net.sf.orcc.tools.classifier.ActorState;
import net.sf.orcc.tools.classifier.GuardSatChecker;
/**
*
*
* @author Johan Ersfolk
*
*/
public class PromelaSchedulabilityTest extends DfVisitor<Void> {
private Scheduler scheduler;
private Set<Port> peekPorts = new HashSet<Port>();
private Set<Transition> transitionsInCycle = new HashSet<Transition>();
private ControlTokenActorModel actorModel;
private Set<State> choiceStatesSet = new HashSet<State>();
private List<State> visitedStatesList = new LinkedList<State>();
private List<Transition> transitionSequenceList = new LinkedList<Transition>();
private Map<Action, Set<Var>> actionGuardVarMap = new HashMap<Action, Set<Var>>();
private Set<Var> inputPortVars = new HashSet<Var>();
private Map<Var, Port> inputVarToPortMap = new HashMap<Var, Port>();
private FSM fsm;
private Action action;
public PromelaSchedulabilityTest(ControlTokenActorModel actorModel) {
super();
this.actorModel=actorModel;
this.irVisitor = new InnerIrVisitor();
}
@Override
public Void caseAction(Action action) {
this.action = action;
// how is this action scheduled
Set<Var> guardVars = new HashSet<Var>();
actionGuardVarMap.put(action, guardVars);
for (Var var : action.getScheduler().getLocals()) {
// find on which variables this scheduler depends
Set<Var> tc = new HashSet<Var>();
actorModel.getTransitiveClosure(var, tc, true);
for (Var v : tc) {
if (v.isGlobal()) {
guardVars.add(v);
}
}
}
doSwitch(action.getBody());
return null;
}
@Override
public Void caseActor(Actor actor) {
System.out.println("\n Sched-test Actor:" + actor.getName());
this.actor=actor;
this.fsm = actor.getFsm();
this.scheduler = new Scheduler(actor, this.fsm);
// find variables corresponding to input ports
for (Action action : actor.getActions()) {
inputPortVars.addAll(action.getInputPattern().getVariables());
for (Var var : action.getInputPattern().getVariables()) {
inputVarToPortMap.put(var, action.getInputPattern()
.getPort(var));
}
}
// how does actions affect scheduling
for (Action action : actor.getActions()) {
doSwitch(action);
}
((InnerIrVisitor)this.irVisitor).resolveDeps();
if (fsm != null) {
// find states where actions are input dependent + initial state
choiceStatesSet.add(fsm.getInitialState());
for (State state : fsm.getStates()) {
identifyChoiceStates(state);
for (Action action : fsm.getTargetActions(state)) {
if (!action.getPeekPattern().getPorts().isEmpty()) {
peekPorts.addAll(action.getPeekPattern().getPorts());
choiceStatesSet.add(state);
}
}
}
// check for nondeterministic cycles in the fsm
for (State state : actor.getFsm().getStates()) {
fsmFindInputdepCycles(state);
}
if (!actor.getActionsOutsideFsm().isEmpty()) {
System.out.println(" == Actor has actions outside FSM:");
for (Action action : actor.getActionsOutsideFsm()) {
if (!action.getInputPattern().isEmpty()) {
System.out.println("Input on port;"
+ action.getInputPattern());
}
if (!action.getOutputPattern().isEmpty()) {
System.out.println("Output on port;"
+ action.getOutputPattern());
}
if (!action.getPeekPattern().isEmpty()) {
System.out.println("Peek on port;"
+ action.getPeekPattern());
}
}
}
} else {
scheduler.addSchedule(new Schedule());
}
actorModel.clearCaches();
boolean stopChecking;
boolean rerunNeeded;
boolean startFromFresh=true;
do {
System.out.println("Scheduling Actor " +actor.getName());
if (startFromFresh) {
if (fsm != null) {
scheduler.getSchedules().clear();
for (State state : choiceStatesSet) {
fsmPathSearch(state);
}
}
if (actor.hasMoC() && actor.getMoC().isDPN()) {
// this is time-dependent, stop the show
System.out.println("Actor " + actor.getSimpleName() + " is time-dependent, we skip it for now..");
scheduler.makeDummyFSM();
break;
}
}
startFromFresh = true;
stopChecking = false;
rerunNeeded = false;
scheduler.buildSchedulingCases();
// Abstract interpretation Start----------------------------------------
final int MAX_PHASES = 1024;
INTERP: for (List<Schedule> sl : scheduler.getScheduleCases()) {
PromelaAbstractInterpreter interpreter = new PromelaAbstractInterpreter(actor);
ActorState actorstate = new ActorState(interpreter.getActor());
for (int index = 0; index < sl.size(); index++) {
Schedule schedule = sl.get(index);
int nbPhases = 0;
try {
if (schedule.getEnablingAction() != null) {
interpreter.setNextPath(schedule.getEnablingAction());
}
do {
interpreter.schedule();
Action latest = interpreter.getExecutedAction();
if (index==sl.size()-1 && !schedule.isScheduleDone()) {
schedule.getSequence().add(latest);
}
nbPhases++;
} while (((actor.hasFsm() && !schedule.getPotentialEndStates().contains(interpreter.getFsmStateOrig()))
|| (!actor.hasFsm() && !actorstate.isInitialState()))
&& nbPhases < MAX_PHASES);
if (nbPhases == MAX_PHASES) {
scheduler.makeDummyFSM();
stopChecking=true;
}
if (actor.hasFsm()) {
if (schedule.getEndState()==null && interpreter.getFsmStateOrig()!=actor.getFsm().getInitialState()) {
startFromFresh=false;
rerunNeeded=true;
}
schedule.setEndState(interpreter.getFsmStateOrig());
}
schedule.setScheduleDone(true);
}catch (OrccRuntimeException e){ //should only happen on native calls
if (actor.hasFsm() && interpreter.nullWasNormal()) {
choiceStatesSet.add(interpreter.getFsmStateOrig());
rerunNeeded=true;
} else {
System.out.println("Actor "+this.actor.getName()+" experienced a loop time-out!!\n\n");
scheduler.makeDummyFSM();
stopChecking=true;
}
break INTERP;
}
}
}
// Abstract interpretation End----------------------------------------
if (!stopChecking && !rerunNeeded) {
stopChecking = areSchedulesComplete();
}
} while (!stopChecking);
for (Schedule s : scheduler.getSchedules()) {
generateScheduleInfo(s);
}
whenIsVarPartOfState();
return null;
}
private boolean areSchedulesComplete() {
// does the schedule itself reset the variables before they are used in guards?
boolean allResolved = true;
Set<State> resolvedStates = new HashSet<State>();
for (Schedule schedule : scheduler.getSchedules()) {
for (State state : schedule.getPotentialChoiseStates()) {
boolean resolved = true; // until proven guilty
resolvedStates.clear();
Set<Var> guardFull = new HashSet<Var>();
Set<Var> guardDirect = new HashSet<Var>();
Set<Action> cActions = new HashSet<Action>();
// The set of variables that can have an impact on the guards on this state
for (Edge edge : state.getOutgoing()) {
cActions.add(((Transition)edge).getAction());
guardFull.addAll(actionGuardVarMap.get(((Transition)edge).getAction()));
guardDirect.addAll(actionGuardVarMap.get(((Transition)edge).getAction()));
Set<Var> temp = new HashSet<Var>();
for (Var g : guardFull) {
actorModel.getTransitiveClosure(g, temp, true);
}
guardFull.addAll(temp);
}
removeLocalAndConstantVars(guardFull);
Set<Var> dirtyVars = new HashSet<Var>(guardFull);
Set<Var> cleanVars = new HashSet<Var>();
getAlwaysCleanVars(state);
// run through the sequence of actions and keep track of which variables are dirty or clean
for (Action action : schedule.getSequence()) {
if (cActions.contains(action)) {
// the action belongs to the potential choice state, check if the guard has dirty vars
for (Var var : dirtyVars) {
if (guardDirect.contains(var)) {
resolved=false;
}
}
}
for (Var gVar : guardFull) {
if (scheduler.getSchedVarReset().containsKey(gVar)
&& scheduler.getSchedVarReset().get(gVar).contains(action)) {
dirtyVars.remove(gVar);
cleanVars.add(gVar);
}
// if the variable is updated from a var that is dirty, this variable is also dirty
if (scheduler.getSchedVarUpdate().containsKey(gVar)
&& scheduler.getSchedVarUpdate().get(gVar).contains(action)
&& cleanVars.contains(gVar)){
Set<Var> temp = scheduler.getLocalVarDep(action, gVar);
for (Var depOn : temp) {
if (dirtyVars.contains(depOn)) {
cleanVars.remove(gVar);
dirtyVars.add(gVar);
}
}
}
}
}
if (resolved) {
resolvedStates.add(state);
}
}
schedule.getPotentialChoiseStates().removeAll(resolvedStates);
if (!schedule.getPotentialChoiseStates().isEmpty()) {
choiceStatesSet.addAll(schedule.getPotentialChoiseStates());
allResolved=false;
}
}
return allResolved;
}
private Set<Var> getAlwaysCleanVars(State state) {
// TODO return something real
return new HashSet<Var>();
}
private void whenIsVarPartOfState() {
Map<State, Set<Var>> stateToVar = scheduler.getStateToRelevantVars();
Set<Var> stateVars = actorModel.getLocalSchedulingVars();
scheduler.setSchedulingVars(stateVars);
removeLocalAndConstantVars(stateVars);
for (Schedule schedule : scheduler.getSchedules()) {
Set<Var> dirtyVars = new HashSet<Var>(stateVars);
Set<Var> cleanVars = new HashSet<Var>();
Set<Var> dirtyInGuard = new HashSet<Var>();
// run through the sequence of actions and keep track of which variables are dirty or clean
for (Action action : schedule.getSequence()) {
for (Var gVar : actionGuardVarMap.get(action)) {
if (dirtyVars.contains(gVar)) {
//definitely part of state
dirtyInGuard.add(gVar);
}
}
for (Var sVar : stateVars) {
if (scheduler.getSchedVarReset().containsKey(sVar)
&& scheduler.getSchedVarReset().get(sVar).contains(action)) {
dirtyVars.remove(sVar);
cleanVars.add(sVar);
}
// if the variable is updated from a var that is dirty, this variable is also dirty
if (scheduler.getSchedVarUpdate().containsKey(sVar)
&& scheduler.getSchedVarUpdate().get(sVar).contains(action)
&& cleanVars.contains(sVar)){
Set<Var> temp = scheduler.getLocalVarDep(action, sVar);
for (Var depOn : temp) {
if (dirtyVars.contains(depOn)) {
cleanVars.remove(sVar);
dirtyVars.add(sVar);
}
}
}
}
}
if (!stateToVar.containsKey(schedule.getInitState())) {
stateToVar.put(schedule.getInitState(), new HashSet<Var>());
}
stateToVar.get(schedule.getInitState()).addAll(dirtyInGuard);
// the following could be made more less to improve the state reduction..
if (schedule.getInitState()!=schedule.getEndState()) {
// this schedule did not use the variable, if we get back to the same state, no harm has been done
stateToVar.get(schedule.getInitState()).addAll(dirtyVars);
}
}
}
private Map<String, Object> findPeekValues(State state, Action targetAction) {
List<Action> previous = new ArrayList<Action>();
Set<Port> peekPorts = new HashSet<Port>();
for (Schedule schedule : scheduler.getSchedulesStartingAt(state)) {
Action action = schedule.getSequence().get(0);//only first actions of a schedule
peekPorts.addAll(action.getPeekPattern().getPorts());
}
if (peekPorts.isEmpty()) {
return new HashMap<String, Object>();
}
Map<String, Object> configuration = null;
List<Action> actions;
if (state!=null) {
actions = fsm.getTargetActions(state);
} else {
actions=actor.getActions();
}
for (Action action : actions) {
if (action == targetAction) {
GuardSatChecker checker = new GuardSatChecker(actor);
try {
configuration = checker.computeTokenValues(new ArrayList<Port>(peekPorts),
previous, targetAction);
} catch (OrccRuntimeException e) {
System.out.println(e.getMessage());
}
break;
} else {
previous.add(action);
}
}
if (configuration==null) { //in case the solver was not available
configuration=new HashMap<String, Object>();
}
return configuration;
}
private Schedule currentSchedule = null;
/**
* Identifies paths between input dependent states
* @param state
*/
private void fsmPathSearch(State state) {
visitedStatesList.add(state);
for (Edge edge : state.getOutgoing()) {
Transition transition = (Transition) edge;
transitionSequenceList.add(transition);
if (choiceStatesSet.contains(state)) {
currentSchedule = new Schedule(state, transition.getAction());
scheduler.addSchedule(currentSchedule);
} else if (state.getOutgoing().size() > 1) {
currentSchedule.addPotentialChoiseState(state);
}
// check successor states
State target = transition.getTarget();
if (choiceStatesSet.contains(target)) {
// this is not a cycle as it ends the current schedule
currentSchedule.addPotentialEndState(target);
} else if (visitedStatesList.contains(target)) {
// non-input dependent cycle inside schedule
for (int i = visitedStatesList.indexOf(target); i < visitedStatesList
.size(); i++) {
transitionsInCycle.add(transitionSequenceList.get(i));
}
} else {
fsmPathSearch(target);
}
transitionSequenceList.remove(transition);
}
visitedStatesList.remove(state); // only keep track of predecessors
return;
}
private void identifyChoiceStates(State state) {
for (Edge edge : state.getOutgoing()) {
Transition transition = (Transition) edge;
for (Var var : actionGuardVarMap.get(transition.getAction())) {
if (hasInputDep(var, true)) {
choiceStatesSet.add(state);
return;
}
}
}
}
private void fsmFindInputdepCycles(State state) {
boolean inputDep = false;
boolean cycle = false;
boolean choice = (state.getOutgoing().size() > 1) ? true : false;
Set<Port> inputPorts = new HashSet<Port>();
for (Edge edge : state.getOutgoing()) {
Transition transition = (Transition) edge;
if (transitionsInCycle.contains(transition)) {
cycle = true;
}
for (Var var : actionGuardVarMap.get(transition.getAction())) {
boolean temp = hasInputDep(var, true);
inputDep = temp || inputDep;
if (temp) {
inputPorts.addAll(getInputDep(var, true));
}
}
}
if (inputDep && cycle && choice) {
System.out.println("State: " + state.getName()
+ " == repetition depends on input value from Port(s): "
+ inputPorts);
}
}
private void generateScheduleInfo(Schedule schedule) {
State initState=schedule.getInitState();
Map<String, List<Object>> portPeeks = schedule.getPortPeeks();
Map<String, List<Object>> portReads = schedule.getPortReads();
Map<String, List<Object>> portWrites = schedule.getPortWrites();
Map<String, Object> configuration = findPeekValues(initState, schedule.getEnablingAction());
for (String key : configuration.keySet()) {
if (!portPeeks.containsKey(key)) {
portPeeks.put(key, new ArrayList<Object>());
}
portPeeks.get(key).add(configuration.get(key));
}
for (Action action : schedule.getSequence()) {
for (Port port : action.getInputPattern().getPorts()) {
if (!portReads.containsKey(port.getName())) {
portReads.put(port.getName(), new ArrayList<Object>());
}
for (int i=0; i<action.getInputPattern().getNumTokens(port); i++) {
portReads.get(port.getName()).add(new Integer(0));
}
}
}
// Control tokens generation
for (Action action : schedule.getSequence()) {
for (Port port : action.getOutputPattern().getPorts()) {
if (!portWrites.containsKey(port.getName())) {
portWrites.put(port.getName(), new ArrayList<Object>());
}
for (int i=0; i<action.getOutputPattern().getNumTokens(port); i++) {
portWrites.get(port.getName()).add(new Integer(0));
}
}
}
}
Set<Port> getInputDep(Var var, boolean includeIfCond) {
Set<Var> tc = new HashSet<Var>();
Set<Port> p = new HashSet<Port>();
actorModel.getTransitiveClosure(var, tc, includeIfCond);
for (Var v : tc) {
if (inputPortVars.contains(v)) {
p.add(inputVarToPortMap.get(v));
}
}
return p;
}
Set<Port> getPeeksOfState(State state) {
Set<Port> statePeeks = new HashSet<Port>();
for (Edge edge : state.getOutgoing()) {
statePeeks.addAll(((Transition) edge).getAction().getPeekPattern()
.getPorts());
}
return statePeeks;
}
boolean hasInputDep(Var var, boolean includeIfCond) {
Set<Var> tc = new HashSet<Var>();
actorModel.getTransitiveClosure(var, tc, includeIfCond);
boolean hasDep = false;
for (Var v : tc) {
if (inputPortVars.contains(v)) {
hasDep = true;
}
}
return hasDep;
}
boolean hasVarLoop(Var var) {
return actorModel.hasLoop(var);
}
boolean isBasedOnConstants(Var var) {
Set<Var> tc = new HashSet<Var>();
actorModel.getTransitiveClosure(var, tc, true);
for (Var v : tc) {
if (v.isLocal()) {
continue;
} else
if (actorModel.getVariableDependency().containsKey(v)) {
return false;
}
}
return true;
}
/**
* A helper method that is used to, from a set remove the variables that are either local to an action or a constant
* @param varSet
*/
public static void removeLocalAndConstantVars(Set<Var> varSet) {
Iterator<Var> i = varSet.iterator();
while (i.hasNext()) {
Var v = i.next();
if (v.isLocal() || !v.isAssignable()) {
i.remove();
}
}
}
private class InnerIrVisitor extends AbstractIrVisitor<Void> {
private Map<InstStore, Var> instToVarMap = new HashMap<InstStore,Var>();
private Map<InstStore, Action> instToActionMap = new HashMap<InstStore, Action>();
private Map<InstStore, List<Var>> instToLocalsMap = new HashMap<InstStore, List<Var>>();
public InnerIrVisitor() {
super(false); //only expressions of store
}
private List<Var> localVars;
@Override
public Void caseInstStore(InstStore store) {
if (!actorModel.getAllReacableSchedulingVars().contains(store.getTarget().getVariable())) {
return null;
}
instToVarMap.put(store, store.getTarget().getVariable());
instToActionMap.put(store, action);
localVars = new ArrayList<Var>();
instToLocalsMap.put(store, localVars);
doSwitch(store.getValue());
return null;
}
@Override
public Void caseExprVar(ExprVar var) {
localVars.add(var.getUse().getVariable());
return null;
}
public void resolveDeps() {
for (InstStore inst : instToLocalsMap.keySet()) {
boolean hasLoop = false;
boolean isConstant = true;
Set<Var> localDep = new HashSet<Var>();
for (Var local : instToLocalsMap.get(inst)) {
if (hasVarLoop(local)) {
hasLoop = true;
}
if (!isBasedOnConstants(local)){
isConstant = false;
}
actorModel.getTransitiveClosure(local, localDep, true);
}
if (hasLoop) {
scheduler.addvarUpdate(inst.getTarget().getVariable(), instToActionMap.get(inst));
}
if (isConstant) {
scheduler.addvarReset(inst.getTarget().getVariable(), instToActionMap.get(inst));
}
if (!hasLoop && !isConstant) {
//System.out.println("Found a scheduling variable which is updated through a action sequence, for now consider it is a pure update");
scheduler.addvarUpdate(inst.getTarget().getVariable(), instToActionMap.get(inst));
}
removeLocalAndConstantVars(localDep);
scheduler.addLocalVarDep(instToActionMap.get(inst), inst.getTarget().getVariable(), localDep);
}
}
}
public Scheduler getScheduler() {
return scheduler;
}
}