package net.sf.orcc.backends.c.dal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.emf.common.util.EMap;
import net.sf.orcc.graph.Edge;
import net.sf.orcc.ir.Block;
import net.sf.orcc.ir.BlockBasic;
import net.sf.orcc.ir.InstCall;
import net.sf.orcc.ir.InstLoad;
import net.sf.orcc.ir.Instruction;
import net.sf.orcc.ir.IrFactory;
import net.sf.orcc.ir.Var;
import net.sf.orcc.ir.impl.IrFactoryImpl;
import net.sf.orcc.ir.util.IrUtil;
import net.sf.orcc.tools.classifier.GuardSatChecker;
import net.sf.orcc.util.OrccLogger;
import net.sf.orcc.backends.c.dal.transform.IfConverter;
import net.sf.orcc.backends.c.dal.transform.LoadRewriter;
import net.sf.orcc.df.Actor;
import net.sf.orcc.df.Action;
import net.sf.orcc.df.State;
import net.sf.orcc.df.Transition;
import net.sf.orcc.df.Pattern;
import net.sf.orcc.df.Port;
import net.sf.orcc.df.Network;
/**
* Class for checking Kahn process networks (KPN) compliance
* of a network.
*
* Theory described in EMSOFT 2015 publication
* "Executing Dataflow Actors as Kahn Processes"
*
* @author Jani Boutellier
* @author James Guthrie
*
*/
public class KPNValidator {
private GuardSatChecker satChecker;
private ArrayList<Action> actions;
private boolean satError;
String path;
private boolean printPeekTree = false;
public KPNValidator(String srcPath) {
path = srcPath;
}
public void validate(Network network) {
IfConverter converter = new IfConverter();
for (Actor originalActor : network.getAllActors()) {
if (originalActor.getFileName() != null) {
converter.ifConvertGuards(originalActor);
Actor actor = IrUtil.copy(originalActor);
boolean isKPN = true;
satError = false;
if (actor.hasFsm()) {
for (State state : actor.getFsm().getStates()) {
if (!inspectState(actor, state, originalActor)) {
isKPN = false;
break;
}
if (satError) {
break;
}
}
} else {
if (actor.getActions().size() >= 1) {
SeqTreeNode root;
root = inspectActionList(actor, actor.getActionsOutsideFsm(), false);
actor.setAttribute("SequenceTreeRoot", root);
if (root == null) {
isKPN = false;
}
}
}
if (satError) {
OrccLogger.noticeln("Actor [" + actor.getName() + "] not classified");
} else {
if (isKPN) {
OrccLogger.noticeln("Actor [" + actor.getName() + "] is KPN");
} else {
OrccLogger.noticeln("Actor [" + actor.getName() + "] could not be translated to KPN");
}
}
}
}
}
public void analyzeInputs(Network network) {
for (Actor actor : network.getAllActors()) {
if (actor.getInputs().size() == 0) {
actor.addAttribute("variableInputPattern");
continue;
}
if (actor.getActions().size() > 1) {
OrccLogger.traceln("Actor " + actor.getName() + " may have variable input rate (more that one action)");
} else {
Pattern inputPattern = actor.getActions().get(0).getInputPattern();
for(Port port : inputPattern.getPorts()) {
port.setNumTokensConsumed(inputPattern.getNumTokensMap().get(port));
}
}
}
}
public void analyzeOutputs(Network network) {
for (Actor actor : network.getAllActors()) {
if (actor.getActions().size() > 1) {
analyzeOutputPorts(actor, actor.getActions());
} else {
if (actor.getActions().size() != 0) {
Pattern outputPattern = actor.getActions().get(0).getOutputPattern();
for(Port port : outputPattern.getPorts()) {
port.setNumTokensProduced(outputPattern.getNumTokensMap().get(port));
}
}
}
}
}
private boolean inspectState(Actor actor, State srcState, Actor originalActor) {
List<Action> actions = new ArrayList<Action>();
for (Edge edge : srcState.getOutgoing()) {
actions.add(((Transition) edge).getAction());
}
actions.addAll(actor.getActionsOutsideFsm());
if (printPeekTree) {
OrccLogger.traceln("State " + srcState.getName());
}
SeqTreeNode root = inspectActionList(actor, actions, false);
srcState.setAttribute("SequenceTreeRoot", root);
if (root == null) {
return false;
} else {
return true;
}
}
private SeqTreeNode inspectActionList(Actor actor, List<Action> actions, boolean actorLevel) {
SeqTreeNode root = getPeekSequence(actor, actions);
if (root != null) {
SeqTreeSearcher searcher = new SeqTreeSearcher(actor);
searcher.search(root, 0, printPeekTree);
SeqTreeNode ndNode = searcher.getNondeterministic();
if (ndNode != null) {
OrccLogger.traceln("Could not arbitrate actor " + actor.getName() + " actions: " + ndNode.getActions().toString());
return null;
}
}
return root;
}
/**
* Get the peek sequence of <code>actor</code> for <code>actions</code>
*
* @param actor
* @param actions
* @return The root node of a peek sequence tree
*/
private SeqTreeNode getPeekSequence(Actor actor, List<Action> actions) {
satChecker = new GuardSatChecker(actor);
this.actions = new ArrayList<Action>(actions);
SeqTreeNode root = new SeqTreeNode(actions);
return addChildren(root);
}
/**
* Recursively construct port peek sequence.
*
* @param current
* @return The root node of a peek sequence tree
*/
private SeqTreeNode addChildren(SeqTreeNode current) {
List<Action> actions = current.getActions();
Set<Token> intersection = getNextReadTokens(actions);
intersection.removeAll(current.getProcessed());
if (!intersection.isEmpty()) {
Set<Token> processed = new HashSet<Token>();
processed.addAll(current.getProcessed());
processed.addAll(intersection);
for (Action a: actions) {
SeqTreeNode node = new SeqTreeNode(new GuardConstraint(a, new TreeSet<Token>(intersection)), a, processed);
if (!insertChildNode(current, node)) {
return null;
}
}
for (SeqTreeNode child : current.getChildren()) {
if (addChildren(child) == null) {
return null;
}
}
} else {
if (actions.size() != 1){
for (Action action : actions) {
if ((current.getConstraints() == null) || !current.getConstraints().equivalent(action)){
return current;
} else {
for (Action other : actions) {
// Use actor actions list here to ensure that ordering is correct
int actionIdx = this.actions.indexOf(action);
int otherIdx = this.actions.indexOf(other);
if ((actionIdx > otherIdx) && !action.getInputPattern().isSupersetOf(other.getInputPattern())) {
return current;
}
}
}
}
}
}
return current;
}
/**
* Insert node as a child of current, if the constraints of node are sat
* with the constraints of an existing child, the two nodes are resolved
* and replaced with child nodes with mutually exclusive constraints.
*
* @param current
* @param node
*/
private boolean insertChildNode(SeqTreeNode current, SeqTreeNode node) {
if (satError) {
return false;
}
List<SeqTreeNode> children = current.getChildren();
if (children.isEmpty()) {
current.addChild(node);
} else {
boolean unsat = true;
for (SeqTreeNode child : current.getChildren()) {
if (sat(node, child) == true) {
unsat = false;
Set<SeqTreeNode> nodes = resolve(node, child);
current.removeChild(child);
for (SeqTreeNode n : nodes) {
insertChildNode(current, n);
}
break;
}
}
if (unsat == true) {
current.addChild(node);
}
}
return true;
}
/**
* Resolve overlapping constraints between left and right, returning
* nodes which have mutually exclusive constraints
*
* @param left
* @param right
* @return set of nodes with mutually exclusive constraints
*/
private Set<SeqTreeNode> resolve(SeqTreeNode left, SeqTreeNode right) {
GuardConstraint intersect = left.getConstraints().intersect(right.getConstraints());
GuardConstraint leftConst = left.getConstraints().difference(right.getConstraints());
GuardConstraint rightConst = right.getConstraints().difference(left.getConstraints());
Set<SeqTreeNode> nodes = new HashSet<SeqTreeNode>();
Set<Token> processed = left.getProcessed();
List<Action> actions = new ArrayList<Action>(left.getActions());
actions.addAll(right.getActions());
SeqTreeNode n;
n = new SeqTreeNode(intersect, actions, processed);
if (sat(n, actions)) {
nodes.add(n);
}
n = new SeqTreeNode(leftConst, left.getActions(), processed);
if (sat(n, actions)) {
nodes.add(n);
}
n = new SeqTreeNode(rightConst, right.getActions(), processed);
if (sat(n, actions)) {
nodes.add(n);
}
return nodes;
}
private void analyzeOutputPorts(Actor actor, List<Action> actions) {
boolean hasBeenWarned = false;
for (Action first : actions) {
for (Action second : actions) {
hasBeenWarned = compareOutputPatterns(actor, first, second, hasBeenWarned);
}
}
}
private boolean compareOutputPatterns(Actor actor, Action firstAction, Action secondAction, boolean hasBeenWarned) {
Pattern first = firstAction.getOutputPattern();
Pattern second = secondAction.getOutputPattern();
for(Port port : first.getPorts()) {
int firstTokenRate = first.getNumTokensMap().get(port);
port.setNumTokensProduced(firstTokenRate);
if (second.getNumTokensMap().get(port) != null) {
int secondTokenRate = second.getNumTokensMap().get(port);
if (firstTokenRate != secondTokenRate) {
if (!hasBeenWarned) {
OrccLogger.traceln("Actor " + actor.getName() + " port " +
port.getName() + " has a variable output rate." +
" This actor has limited OpenCL compatibility.");
hasBeenWarned = true;
}
port.setNumTokensProduced(-1);
}
} else {
if (second.getNumTokensMap().size() > 0) {
if (!hasBeenWarned) {
OrccLogger.traceln("Actor " + actor.getName() + " action " +
firstAction.getName() + " writes port " +
port.getName() + " but action "+ secondAction.getName() +
" does not. This actor has limited OpenCL compatibility.");
hasBeenWarned = true;
}
}
}
}
return hasBeenWarned;
}
/**
* Determine whether constraints of nodes left and right
* are simultaneously satisfiable
*
* @param left
* @param right
* @return true if satisfiable, false if mutually exclusive
*/
private boolean sat(SeqTreeNode left, SeqTreeNode right) {
// Clone existing actions
Set<Action> actions = new HashSet<Action>();
actions.addAll(left.getActions());
actions.addAll(right.getActions());
Action leftAction = IrUtil.copy(getPeekiestAction(actions));
Action rightAction = IrUtil.copy(getPeekiestAction(actions));
// Set constraints of cloned actions
boolean leftOk = left.getConstraints().setConstraint(leftAction);
boolean rightOk =right.getConstraints().setConstraint(rightAction);
if (!leftOk || !rightOk) {
satError = true;
return false;
}
boolean result = satChecker.checkSat(leftAction, rightAction);
satError |= satChecker.hasFailed();
return result;
}
/**
* Determine whether constraints of node are satisfiable
*
* @param node
* @return true if satisfiable, false otherwise
*/
private boolean sat(SeqTreeNode node, Collection<Action> actions) {
// Clone existing action
Action action = IrUtil.copy(getPeekiestAction(actions));
// Set constraints of cloned action
if (!node.getConstraints().setConstraint(action)) {
satError = true;
return false;
}
boolean result = satChecker.checkSat(action);
satError |= satChecker.hasFailed();
return result;
}
private Action getPeekiestAction(Collection<Action> actions) {
Iterator<Action> iter = actions.iterator();
Action action = iter.next();
while (iter.hasNext()) {
Action a = iter.next();
if (a.getPeekPattern().isSupersetOf(action.getPeekPattern())) {
action = a;
}
}
return action;
}
/**
* Get the tokens which can be read by all actions in a DAL KPN.
* This is the intersection of the input tokens of all actions and
* the union of state tokens of all actions
*
* @param actions
* @return A Set of tokens
*/
private Set<Token> getNextReadTokens(Collection<Action> actions) {
Set<Token> theseTokens = new HashSet<Token>();
Set<Set<Token>> allInputTokens = new HashSet<Set<Token>>();
for (Action a : actions){
Set<Token> inputTokens = getInputTokens(a);
allInputTokens.add(inputTokens);
}
Set<Token> intersection = getIntersection(allInputTokens);
Set<Var> deps = new HashSet<Var>();
for (Token t : intersection) {
if (t instanceof LoadTokenImpl) {
deps.add(((LoadTokenImpl) t).getInstruction().getTarget().getVariable());
}
}
for (Action a : actions){
Set<Token> stateTokens = getStateTokens(a, deps);
theseTokens.addAll(stateTokens);
}
Set<Token> nextRead = new TreeSet<Token>();
nextRead.addAll(intersection);
nextRead.addAll(theseTokens);
return nextRead;
}
/**
* Get state tokens belonging to the scheduler of <code>action</code>
* given the <code>inputVars</code> which are defined by the input
* tokens.
*
* @param action
* @return A Set of tokens
*/
private Set<Token> getStateTokens(Action action, Collection<Var> inputVars) {
Set<Token> tokens = new TreeSet<Token>();
Set<Var> vars = new HashSet<Var>(inputVars);
for (Block b : action.getScheduler().getBlocks()) {
if (b instanceof BlockBasic) {
for (Instruction i : ((BlockBasic) b).getInstructions()) {
if (i instanceof InstLoad || i instanceof InstCall) {
Token t;
if (i instanceof InstLoad) {
t = new LoadTokenImpl((InstLoad) i);
} else {
t = new CallTokenImpl((InstCall) i);
}
if (t.isStateToken()) {
tokens.add(t);
vars.add(t.getTargetVar());
}
}
}
}
}
// Remove tokens whose dependencies are not fulfilled
for (Iterator<Token> it = tokens.iterator(); it.hasNext(); ) {
Token aToken = it.next();
if (!aToken.depsFulfilledBy(vars)) {
it.remove();
}
}
return tokens;
}
/**
* Get the input tokens belonging to the scheduler of <code>action</code>.
*
* @param actor
* @return A Set of load instructions
*/
private Set<Token> getInputTokens(Action action) {
Set<Token> tokens = new TreeSet<Token>();
Pattern pattern = action.getInputPattern();
EMap <Port, Var> m = pattern.getPortToVarMap();
IrFactory irFactory = new IrFactoryImpl();
for (Block b : action.getScheduler().getBlocks()) {
if (b.isBlockBasic()) {
for (Port port : m.keySet()) {
Var v = m.get(port);
for (int i = 0; i < pattern.getNumTokens(port); i++) {
Var target = irFactory.createVar(port.getType(), "placeholder_name" , false, i);
InstLoad load = irFactory.createInstLoad(target, v, i);
new LoadRewriter().doSwitch(load);
tokens.add(new LoadTokenImpl(load));
}
}
}
}
return tokens;
}
/**
* Get the set intersection. Given a set of Sets, this function determines
* the intersection of all Sets and returns the elements in the intersection
*
* @param s a set of Sets
* @return a set representing the intersection of the values of the Sets
*/
private <E> Set<E> getIntersection(Set<Set<E>> s){
Iterator<Set<E>> it = s.iterator();
if (it.hasNext()) {
Set<E> result = new HashSet<E>(it.next());
while (it.hasNext()) {
Set<E> others = it.next();
result.retainAll(others);
}
return result;
} else {
return null;
}
}
}