/*
* Copyright (c) 2009-2010, 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.tools.classifier;
import static net.sf.orcc.OrccActivator.getDefault;
import static net.sf.orcc.moc.MocFactory.eINSTANCE;
import static net.sf.orcc.preferences.PreferenceConstants.P_SOLVER;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.DfFactory;
import net.sf.orcc.df.FSM;
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.df.util.DfVisitor;
import net.sf.orcc.graph.Edge;
import net.sf.orcc.graph.Vertex;
import net.sf.orcc.graph.visit.BFS;
import net.sf.orcc.moc.CSDFMoC;
import net.sf.orcc.moc.Invocation;
import net.sf.orcc.moc.MoC;
import net.sf.orcc.moc.MocFactory;
import net.sf.orcc.moc.QSDFMoC;
import net.sf.orcc.moc.SDFMoC;
import net.sf.orcc.tools.merger.action.GuardInternalizer;
import net.sf.orcc.util.OrccLogger;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
/**
* This class defines an actor classifier that uses symbolic execution to
* classify an actor as static, cyclo-static, quasi-static, or dynamic.
*
* @author Matthieu Wipliez
*
*/
public class Classifier extends DfVisitor<Void> {
private boolean internalizeGuards;
private boolean hasSolver;
// This is used in toString(), to display the actor classified even when
// this.actor has been reseted to null
private String lastActorName;
public Classifier() {
this(false);
}
public Classifier(boolean internalizeGuards) {
this.internalizeGuards = internalizeGuards;
this.hasSolver = !getDefault().getPreference(P_SOLVER, "").isEmpty();
if (!hasSolver) {
OrccLogger.noticeln("The classifier cannot check "
+ "time-dependency since no SMT solver is setup.");
}
}
/**
* Tries to evaluate the guards to check if they are compatible.
*
* @param previous
* the action that occurs before <code>action</code>
* @param action
* an action
* @return <code>true</code> if the guards of the given actions are
* compatible
*/
private boolean areGuardsCompatible(Action previous, Action action) {
if (!hasSolver) {
return true;
}
GuardSatChecker checker = new GuardSatChecker(actor);
try {
if (checker.checkSat(previous, action)) {
OrccLogger.traceln(actor.getName() + ": guards of actions "
+ previous.getName() + " and " + action.getName()
+ " are compatible");
return true;
}
return false;
} catch (OrccRuntimeException e) {
OrccLogger.warnln(actor.getName()
+ ": could not check time-dependency (" + e.getMessage()
+ ")");
return true;
}
}
@Override
public Void caseActor(Actor actor) {
if (actor.isNative()) {
return null;
}
try {
this.actor = actor;
lastActorName = actor.getName();
if (internalizeGuards) {
// Internalize possible guards to avoid wrong classification
new GuardInternalizer().transform(actor);
}
actor.resetTokenConsumption();
actor.resetTokenProduction();
classify();
} catch (Exception e) {
MoC moc = MocFactory.eINSTANCE.createDPNMoC();
actor.setMoC(moc);
OrccLogger.warnln("An exception occurred when classifying actor ("
+ e.getMessage() + ")");
OrccLogger.traceln("MoC of " + actor.getName() + ": " + moc);
} finally {
this.actor = null;
}
return null;
}
/**
* Classifies the actor as dynamic, quasi-static, or static.
*
* @return the class of the actor
*/
private void classify() {
try {
if (actor.getFile() != null) {
IMarker[] markers = actor.getFile().findMarkers(
IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
for (IMarker marker : markers) {
if (marker.getAttribute(IMarker.SEVERITY,
IMarker.SEVERITY_ERROR) == IMarker.SEVERITY_INFO) {
marker.delete();
}
}
}
} catch (CoreException e) {
}
MoC moc;
List<Action> actions = actor.getActions();
if (actions.isEmpty()) {
// checks for empty actors
OrccLogger.traceln("Actor " + actor.getName()
+ " does not contain any actions, defaults to dynamic");
moc = MocFactory.eINSTANCE.createKPNMoC();
} else if (isTimeDependent()) {
// checks for actors with time-dependent behavior
moc = MocFactory.eINSTANCE.createDPNMoC();
showMarker();
} else {
// first tries SDF with *all* the actions of the actor
moc = classifySDF(actions);
if (!moc.isSDF()) {
try {
// not SDF, tries CSDF
moc = classifyCSDF();
} catch (OrccRuntimeException e) {
// data-dependent behavior
}
if (!moc.isCSDF()) {
// not CSDF, tries QSDF
if (actor.hasFsm()) {
try {
moc = classifyQSDF();
} catch (OrccRuntimeException e) {
// data-dependent behavior
}
}
}
}
}
// set and print MoC
actor.setMoC(moc);
OrccLogger.traceln("MoC of " + actor.getName() + ": " + moc);
}
/**
* Tries to classify an actor as CSDF. Classification works only on actor
* with a non-empty state. The state consists of all scalar state variables
* that have an initial value.
*
* @return an actor class
*/
private MoC classifyCSDF() {
AbstractInterpreter interpreter = new AbstractInterpreter(actor);
ActorState state = new ActorState(interpreter.getActor());
if (state.isEmpty()) {
FSM fsm = actor.getFsm();
if (fsm == null || !isCycloStaticFsm(fsm)) {
// no state, no cyclo-static FSM => dynamic
return MocFactory.eINSTANCE.createKPNMoC();
}
}
// creates the MoC
CSDFMoC csdfMoc = MocFactory.eINSTANCE.createCSDFMoC();
int nbPhases = 0;
// loops until the actor goes back to the initial state, or there is a
// data-dependent condition
final int MAX_PHASES = 16384;
State initialState = interpreter.getFsmState();
do {
interpreter.schedule();
Action latest = interpreter.getExecutedAction();
Invocation invocation = eINSTANCE.createInvocation(latest);
csdfMoc.getInvocations().add(invocation);
nbPhases++;
} while ((!state.isInitialState() || interpreter.getFsmState() != initialState)
&& nbPhases < MAX_PHASES);
if (nbPhases == MAX_PHASES) {
return MocFactory.eINSTANCE.createKPNMoC();
}
interpreter.setTokenRates(csdfMoc);
csdfMoc.setNumberOfPhases(nbPhases);
return csdfMoc;
}
/**
* Classify the FSM of the actor this unroller was created with, starting
* from the given initial state, and using the given configuration.
*
* @param configuration
* a configuration
* @return a static class
*/
private MoC classifyFsmConfiguration(Map<String, Object> configuration) {
CSDFMoC csdfMoc = MocFactory.eINSTANCE.createCSDFMoC();
AbstractInterpreter interpreter = new AbstractInterpreter(actor);
State initialState = interpreter.getFsmState();
interpreter.setConfiguration(configuration);
// schedule the actor
int nbPhases = 0;
final int MAX_PHASES = 16384;
do {
interpreter.schedule();
Action latest = interpreter.getExecutedAction();
Invocation invocation = eINSTANCE.createInvocation(latest);
csdfMoc.getInvocations().add(invocation);
nbPhases++;
} while (!interpreter.getFsmState().equals(initialState)
&& nbPhases < MAX_PHASES);
if (nbPhases == 1) {
SDFMoC sdfMoc = MocFactory.eINSTANCE.createSDFMoC();
interpreter.setTokenRates(sdfMoc);
return sdfMoc;
} else if (nbPhases == MAX_PHASES) {
throw new OrccRuntimeException("too many phases");
}
interpreter.setTokenRates(csdfMoc);
csdfMoc.setNumberOfPhases(nbPhases);
return csdfMoc;
}
/**
* Tries to classify this actor with an FSM as QSDF.
*
* @return an MoC
*/
private MoC classifyQSDF() {
FSM fsm = actor.getFsm();
String name = actor.getName();
if (!isQuasiStaticFsm(fsm)) {
OrccLogger.traceln(name
+ " has an FSM that is NOT compatible with quasi-static");
return MocFactory.eINSTANCE.createKPNMoC();
}
State initialState = fsm.getInitialState();
// analyze the configuration of this actor
List<Port> ports = findConfigurationPorts(fsm);
if (ports.isEmpty()) {
OrccLogger.traceln("no configuration ports found for " + name);
return MocFactory.eINSTANCE.createKPNMoC();
}
Map<Action, Map<String, Object>> configurations = findConfigurationValues(
fsm, ports);
if (configurations.isEmpty()) {
OrccLogger.traceln("no configurations for " + name);
return MocFactory.eINSTANCE.createKPNMoC();
}
// will unroll for each branch departing from the initial state
QSDFMoC quasiStatic = MocFactory.eINSTANCE.createQSDFMoC();
quasiStatic.getConfigurationPorts().addAll(ports);
for (Action action : fsm.getTargetActions(initialState)) {
Map<String, Object> configuration = configurations.get(action);
if (configuration == null) {
OrccLogger.traceln("no configuration for " + action.getName());
return MocFactory.eINSTANCE.createKPNMoC();
}
MoC staticClass = classifyFsmConfiguration(configuration);
quasiStatic.setMoC(action, staticClass);
}
return quasiStatic;
}
/**
* Tries to classify an actor as SDF. An actor is SDF if it has one action.
* If an actor has many actions (in a given state if it has an FSM) with the
* same input/output patterns, then these actions are merged by the
* SDFActionsMerger transformation.
*
* @param actions
* a list of actions sorted by descending priority
* @return a Model of Computation
*/
private MoC classifySDF(List<Action> actions) {
int numActions = actions.size();
if (numActions != 1) {
// two cases: 1) an empty actor is considered dynamic because the
// only actors with no actions are system actors such as source and
// display
// 2) an actor with many actions after the SDFActionMerger
// transformation has been applied is dynamic
return MocFactory.eINSTANCE.createKPNMoC();
}
// schedule
SDFMoC sdfMoc = MocFactory.eINSTANCE.createSDFMoC();
AbstractInterpreter interpreter = new AbstractInterpreter(actor);
Action initializeAction = interpreter.getExecutedAction();
if (initializeAction != null) {
// If there was an initialize action, the tokens produced
// are delays in the sdf moc.
sdfMoc.getDelayPattern().updatePattern(
(initializeAction.getOutputPattern()));
// Reset output production
interpreter.getActor().resetTokenProduction();
}
interpreter.schedule();
Action action = interpreter.getExecutedAction();
Invocation invocation = eINSTANCE.createInvocation(action);
sdfMoc.getInvocations().add(invocation);
interpreter.setTokenRates(sdfMoc);
return sdfMoc;
}
/**
* Finds the configuration ports of this actor, if any.
*/
private List<Port> findConfigurationPorts(FSM fsm) {
List<Set<Port>> actionPorts = new ArrayList<Set<Port>>();
State initialState = fsm.getInitialState();
// visits the scheduler of each action departing from the initial state
for (Action action : fsm.getTargetActions(initialState)) {
Set<Port> candidates = new HashSet<Port>();
candidates.addAll(action.getPeekPattern().getPorts());
actionPorts.add(candidates);
}
// add all ports peeked
Set<Port> candidates = new HashSet<Port>();
for (Set<Port> set : actionPorts) {
candidates.addAll(set);
}
// and then only retains the ones that are common to every action
for (Set<Port> set : actionPorts) {
if (!set.isEmpty()) {
candidates.retainAll(set);
}
}
// copies the candidates to the ports list
return new ArrayList<Port>(candidates);
}
/**
* For each action departing from the initial state, visits its guards and
* stores a constrained variable that will contain the value to read from
* the configuration port when solved.
*/
private Map<Action, Map<String, Object>> findConfigurationValues(FSM fsm,
List<Port> ports) {
Map<Action, Map<String, Object>> configurations = new HashMap<Action, Map<String, Object>>();
List<Action> previous = new ArrayList<Action>();
// visits the scheduler of each action departing from the initial state
State initialState = fsm.getInitialState();
for (Action targetAction : fsm.getTargetActions(initialState)) {
// create the configuration for this action
GuardSatChecker checker = new GuardSatChecker(actor);
Map<String, Object> configuration = checker.computeTokenValues(
ports, previous, targetAction);
// add the configuration
configurations.put(targetAction, configuration);
// add current action to "previous" list
previous.add(targetAction);
}
return configurations;
}
/**
* Returns <code>true</code> if the given FSM looks like the FSM of a
* cyclo-static actor, <code>false</code> otherwise. A potentially
* cyclo-static actor is an actor with an FSM that cycles back to its
* initial state.
*
* @param fsm
* a Finite State Machine
* @return <code>true</code> if the given FSM has cyclo-static form
*/
private boolean isCycloStaticFsm(FSM fsm) {
State initialState = fsm.getInitialState();
for (Vertex vertex : initialState.getSuccessors()) {
BFS bfs = new BFS(vertex);
if (!bfs.getVertices().contains(initialState)) {
// no path back to initial state: cannot be cyclo-static
return false;
}
}
return true;
}
/**
* Returns <code>true</code> if the given FSM looks like the FSM of a
* quasi-static actor, <code>false</code> otherwise. We simply check that
* for each outgoing edge of the initial state, there is a path back to the
* initial state.
*
* @param fsm
* a Finite State Machine
* @return <code>true</code> if the given FSM has quasi-static form
*/
private boolean isQuasiStaticFsm(FSM fsm) {
State initialState = fsm.getInitialState();
List<Edge> edges = initialState.getOutgoing();
for (Edge edge : edges) {
State target = (State) edge.getTarget();
BFS bfs = new BFS(target);
if (!bfs.getVertices().contains(initialState)) {
// if no vertex goes back to the initial state, return false
return false;
}
}
return true;
}
/**
* Returns <code>true</code> if this actor has a time-dependent behavior.
*
* @return <code>true</code> if this actor has a time-dependent behavior
*/
private boolean isTimeDependent() {
if (actor.hasFsm()) {
FSM fsm = actor.getFsm();
for (State state : fsm.getStates()) {
// add anonymous actions
List<Action> actions = new ArrayList<Action>(
actor.getActionsOutsideFsm());
for (Edge edge : state.getOutgoing()) {
Transition transition = (Transition) edge;
actions.add(transition.getAction());
}
if (isTimeDependent(actions)) {
return true;
}
}
return false;
} else {
return isTimeDependent(actor.getActionsOutsideFsm());
}
}
/**
* Returns <code>true</code> if the given list of actions has a
* time-dependent behavior.
*
* @param actions
* a list of actions
* @return <code>true</code> if the given list of actions has a
* time-dependent behavior
*/
private boolean isTimeDependent(List<Action> actions) {
Iterator<Action> it = actions.iterator();
Pattern higherPriorityPattern = DfFactory.eINSTANCE.createPattern();
List<Action> higherPriorityActions = new ArrayList<Action>();
if (it.hasNext()) {
// initialization with the first, higher priority action
Action higherPriorityAction = it.next();
higherPriorityPattern.updatePattern(higherPriorityAction
.getInputPattern());
higherPriorityActions.add(higherPriorityAction);
// other actions are compared to the higher priority action and
// pattern
while (it.hasNext()) {
Action lowerPriorityAction = it.next();
Pattern lowerPriorityPattern = lowerPriorityAction
.getInputPattern();
if (!lowerPriorityPattern.isSupersetOf(higherPriorityPattern)) {
for (Action action : higherPriorityActions) {
Pattern pattern = action.getInputPattern();
if (!lowerPriorityPattern.isSupersetOf(pattern)) {
if (areGuardsCompatible(action, lowerPriorityAction)) {
return true;
}
}
}
}
// Add the current action to higherPriorityActions
higherPriorityActions.add(lowerPriorityAction);
higherPriorityPattern.updatePattern(lowerPriorityPattern);
}
}
return false;
}
private void showMarker() {
try {
IMarker marker = actor.getFile().createMarker(IMarker.PROBLEM);
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
marker.setAttribute(IMarker.MESSAGE,
"Actor " + actor.getSimpleName() + " is time-dependent");
} catch (CoreException e) {
}
}
@Override
public String toString() {
return "Classifier[" + lastActorName + "]";
}
}