/*
* Copyright (c) 2010-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.tools.classifier;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
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.Unit;
import net.sf.orcc.df.util.DfVisitor;
import net.sf.orcc.ir.Arg;
import net.sf.orcc.ir.ArgByVal;
import net.sf.orcc.ir.ExprBinary;
import net.sf.orcc.ir.ExprBool;
import net.sf.orcc.ir.ExprInt;
import net.sf.orcc.ir.ExprString;
import net.sf.orcc.ir.ExprUnary;
import net.sf.orcc.ir.ExprVar;
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.InstReturn;
import net.sf.orcc.ir.InstStore;
import net.sf.orcc.ir.IrFactory;
import net.sf.orcc.ir.Param;
import net.sf.orcc.ir.Procedure;
import net.sf.orcc.ir.Type;
import net.sf.orcc.ir.TypeBool;
import net.sf.orcc.ir.TypeInt;
import net.sf.orcc.ir.TypeList;
import net.sf.orcc.ir.TypeString;
import net.sf.orcc.ir.TypeUint;
import net.sf.orcc.ir.Use;
import net.sf.orcc.ir.Var;
import net.sf.orcc.ir.util.AbstractIrVisitor;
import net.sf.orcc.ir.util.IrSwitch;
import net.sf.orcc.ir.util.TypePrinter;
import net.sf.orcc.tools.classifier.smt.SmtScript;
import net.sf.orcc.tools.classifier.smt.SmtSolver;
import net.sf.orcc.util.sexp.SExp;
import net.sf.orcc.util.sexp.SExpList;
import net.sf.orcc.util.sexp.SExpSymbol;
import org.eclipse.emf.ecore.EObject;
/**
* This class defines a satisfiability checker for guards of actions. The
* checker checks that the guards of two actions or more are mutually exclusive
* with an SMT solver.
*
* @author Matthieu Wipliez
*
*/
public class GuardSatChecker {
/**
* This class defines a visitor that examines expressions that depend on a
* value peeked from the configuration port.
*
* @author Matthieu Wipliez
*
*/
private static class SmtIrTranslator extends AbstractIrVisitor<Object> {
private StringBuilder builder;
private int numLets;
/**
* list of procedures declared in this script
*/
private List<Procedure> procs;
private SmtScript script;
private boolean translatorFailure;
/**
* Creates a new constraint expression visitor.
*
*/
public SmtIrTranslator() {
script = new SmtScript();
procs = new ArrayList<Procedure>();
}
/**
* Adds an assertion that the given variable equals the given value.
*
* @param variable
* a variable
* @param value
* an expression
*/
private void addLet(Var variable, Expression value) {
addLet(variable, (String) doSwitch(value));
}
/**
* Adds an assertion that the given variable equals the given term.
*
* @param variable
* a variable
* @param term
* a term
*/
private void addLet(Var variable, String term) {
builder.append("(let ((");
builder.append(variable.getName());
builder.append(" ");
builder.append(term);
builder.append(")) ");
numLets++;
}
@Override
public Object caseExprBinary(ExprBinary expr) {
String e1 = (String) doSwitch(expr.getE1());
String e2 = (String) doSwitch(expr.getE2());
switch (expr.getOp()) {
case BITAND:
return "(bvand " + e1 + " " + e2 + ")";
case BITOR:
return "(bvor " + e1 + " " + e2 + ")";
case BITXOR:
return "(bvand (bvnot (bvand " + e1 + " " + e2 + ")) (bvor "
+ e1 + " " + e2 + "))";
case DIV:
case DIV_INT:
return "(bvudiv " + e1 + " " + e2 + ")";
case EQ:
return "(= " + e1 + " " + e2 + ")";
case GE:
return "(not (bvslt " + e1 + " " + e2 + "))";
case GT:
return "(and (not (= " + e1 + " " + e2 + ")) (not (bvslt " + e1
+ " " + e2 + ")))";
case LE:
return "(or (= " + e1 + " " + e2 + ") (bvslt " + e1 + " " + e2
+ "))";
case LOGIC_AND:
return "(and " + e1 + " " + e2 + ")";
case LOGIC_OR:
return "(or " + e1 + " " + e2 + ")";
case LT:
return "(bvslt " + e1 + " " + e2 + ")";
case MINUS:
return "(bvadd " + e1 + " (bvneg " + e2 + "))";
case MOD:
return "(bvurem " + e1 + " " + e2 + ")";
case NE:
return "(not (= " + e1 + " " + e2 + "))";
case PLUS:
return "(bvadd " + e1 + " " + e2 + ")";
case SHIFT_LEFT:
return "(bvshl " + e1 + " " + e2 + ")";
case SHIFT_RIGHT:
return "(bvlshr " + e1 + " " + e2 + ")";
case TIMES:
return "(bvmul " + e1 + " " + e2 + ")";
default:
return "TODO";
}
}
@Override
public Object caseExprBool(ExprBool expr) {
return String.valueOf(expr.isValue());
}
@Override
public Object caseExprInt(ExprInt expr) {
return getStringOfInt(expr.getValue());
}
@Override
public Object caseExprString(ExprString expr) {
return getStringOfInt(expr.getValue().hashCode());
}
@Override
public Object caseExprUnary(ExprUnary expr) {
String e1 = (String) doSwitch(expr.getExpr());
switch (expr.getOp()) {
case BITNOT:
return "(bvnot " + e1 + ")";
case LOGIC_NOT:
return "(not " + e1 + ")";
case MINUS:
return "(bvneg " + e1 + ")";
case NUM_ELTS: {
Type type = expr.getExpr().getType();
int size;
if (type.isList()) {
size = ((TypeList) type).getSize();
} else {
size = 0;
}
return getStringOfInt(BigInteger.valueOf(size));
}
default:
return null;
}
}
@Override
public Object caseExprVar(ExprVar expr) {
Var variable = expr.getUse().getVariable();
EObject cter = variable.eContainer();
if ((cter instanceof Actor || cter instanceof Unit)
&& !script.getVariables().contains(variable)) {
// an expr var may contain a reference to a global var (array)
declareVar(variable);
}
return getUniqueName(variable);
}
@Override
public Object caseInstAssign(InstAssign assign) {
addLet(assign.getTarget().getVariable(), assign.getValue());
return null;
}
@Override
public Object caseInstCall(InstCall call) {
if (call.getTarget() != null) {
Procedure proc = call.getProcedure();
if (!procs.contains(proc)) {
doSwitch(proc);
}
Var variable = call.getTarget().getVariable();
String args = new String();
for (Arg arg : call.getArguments()) {
if (arg.isByVal()) {
Expression expr = ((ArgByVal) arg).getValue();
args += " " + doSwitch(expr);
}
}
String term = new String();
if (args.isEmpty()) {
term = call.getProcedure().getName();
} else {
term = "(" + call.getProcedure().getName() + args + ")";
}
addLet(variable, term);
}
return null;
}
@Override
public Object caseInstLoad(InstLoad load) {
Use source = load.getSource();
Var variable = source.getVariable();
EObject cter = variable.eContainer();
if ((cter instanceof Actor || cter instanceof Unit)
&& !script.getVariables().contains(variable)) {
// might be necessary to declare the variable loaded
declareVar(variable);
}
String term = getUniqueName(variable);
for (Expression index : load.getIndexes()) {
term = "(select " + term + " " + doSwitch(index) + ")";
}
addLet(load.getTarget().getVariable(), term);
return null;
}
@Override
public Object caseInstReturn(InstReturn instReturn) {
builder.append(doSwitch(instReturn.getValue()));
for (; numLets > 0; numLets--) {
builder.append(")");
}
return null;
}
@Override
public Object caseInstStore(InstStore store) {
// if (store.getIndexes().isEmpty()) {
// addLet(store.getTarget().getVariable(), store.getValue());
// }
System.err.println("store");
return null;
}
@Override
public Object caseProcedure(Procedure procedure) {
if (procedure.isNative()) {
if (isAlreadyDeclared(procedure.getName(), "declare-fun")) {
return null;
}
} else {
if (isAlreadyDeclared(procedure.getName(), "define-fun")) {
return null;
}
}
// add procedure to list of procedures
procs.add(procedure);
// save state
StringBuilder oldBuilder = builder;
int oldNumLets = numLets;
// start definition
numLets = 0;
builder = new StringBuilder();
if (procedure.isNative()) {
// cannot define a native function/procedure
builder.append("(declare-fun ");
} else {
builder.append("(define-fun ");
}
builder.append(procedure.getName());
// parameters
builder.append(" (");
for (Param param : procedure.getParameters()) {
Var var = param.getVariable();
builder.append("(");
builder.append(var.getName());
String type = new TypeSwitchBitVec().doSwitch(var.getType());
builder.append(" ");
builder.append(type);
builder.append(") ");
}
builder.append(") ");
// return type
String type = new TypeSwitchBitVec().doSwitch(procedure
.getReturnType());
builder.append(type);
builder.append(" ");
// body
if (!procedure.isNative()) {
super.caseProcedure(procedure);
}
builder.append(")");
// add declaration to script
script.addCommand("");
script.addCommand(builder.toString());
// restore state
builder = oldBuilder;
numLets = oldNumLets;
return null;
}
/**
* Check whether this variable already exists.
*
* @param name
* name of the object
* @param prefix
* the type of the declaration
* @return a boolean value
*/
private boolean isAlreadyDeclared(String name, String prefix) {
for (String defined : script.getCommands()) {
String cmp = new String("(" + prefix + " " + name + " ");
if (defined.startsWith(cmp)) {
return true;
}
}
return false;
}
/**
* Declares the variable with the given name.
*
* @param variable
* a variable
*/
private void declareVar(Var variable) {
String name = getUniqueName(variable);
if (isAlreadyDeclared(name, "declare-fun")) {
return;
}
String type = new TypeSwitchBitVec().doSwitch(variable.getType());
script.addCommand("(declare-fun " + name + " () " + type + ")");
script.getVariables().add(variable);
// constant
if (!variable.isAssignable() && variable.isInitialized()) {
String term = (String) doSwitch(variable.getInitialValue());
if (term != null) {
script.addCommand("(assert (= " + name + " " + term + "))");
}
}
}
/**
* Returns the 32-bit BitVec representation of the given BigInteger.
*
* @param integer
* a BigInteger
* @return the 32-bit BitVec representation
*/
private String getStringOfInt(BigInteger integer) {
if (integer.signum() == -1) {
BigInteger m = BigInteger.valueOf((long) 1 << 32);
integer = m.add(integer);
}
return "(_ bv" + integer + " 32)";
}
/**
* Returns the 32-bit BitVec representation of the given BigInteger.
*
* @param integer
* a BigInteger
* @return the 32-bit BitVec representation
*/
private String getStringOfInt(int integer) {
return "(_ bv" + integer + " 32)";
}
/**
* Returns the name of the given variable so that it does not conflict
* with variables with a similar name declared in other procedures.
*
* @param variable
* a variable
* @return a unique name for the given variable
*/
private String getUniqueName(Var variable) {
String name = variable.getName();
EObject cter = variable.eContainer();
if (cter instanceof Procedure && variable.getType().isList()) {
return ((Procedure) cter).getName() + "_" + name;
}
return name;
}
public void resetFailure() {
translatorFailure = false;
}
public boolean hasFailed() {
return translatorFailure;
}
}
/**
* This class defines a visitor that examines expressions that depend on a
* value peeked from the configuration port.
*
* @author Matthieu Wipliez
*
*/
private static class SmtTranslator extends DfVisitor<Object> {
private SmtIrTranslator irTranslator;
private boolean translatorFailure;
public SmtTranslator() {
irTranslator = new SmtIrTranslator();
irVisitor = irTranslator;
}
@Override
public Object caseAction(Action action) {
Pattern pattern = action.getPeekPattern();
for (Port port : pattern.getPorts()) {
Var variable = pattern.getVariable(port);
irTranslator.declareVar(variable);
int numTokens = pattern.getNumTokens(port);
String command = irTranslator.getUniqueName(variable);
for (int i = 0; i < numTokens; i++) {
String select = "(select " + port.getName() + " "
+ irTranslator.getStringOfInt(i) + ")";
command = "(store " + command + " "
+ irTranslator.getStringOfInt(i) + " " + select
+ ")";
}
command = "(assert (= " + irTranslator.getUniqueName(variable)
+ " " + command + "))";
irTranslator.script.addCommand(command);
}
translatorFailure = false;
irTranslator.resetFailure();
irTranslator.doSwitch(action.getScheduler());
translatorFailure = irTranslator.hasFailed();
return null;
}
@Override
public Object casePort(Port port) {
String name = port.getName();
Type portType = IrFactory.eINSTANCE.createTypeList(0,
port.getType());
String type = new TypeSwitchBitVec().doSwitch(portType);
irTranslator.script.addCommand("(declare-fun " + name + " () "
+ type + ")");
return null;
}
/**
* Returns the script created by this translator.
*
* @return the script created by this translator
*/
public SmtScript getScript() {
return irTranslator.script;
}
public boolean hasFailed() {
return translatorFailure;
}
}
/**
* This class defines a switch that returns the SMT-LIB type using BitVec
* for integers.
*
* @author Matthieu Wipliez
*
*/
private static class TypeSwitchBitVec extends IrSwitch<String> {
@Override
public String caseType(Type type) {
throw new OrccRuntimeException("unexpected type "
+ new TypePrinter().doSwitch(type));
}
@Override
public String caseTypeBool(TypeBool type) {
return "Bool";
}
@Override
public String caseTypeInt(TypeInt type) {
int size = 32; // type.getSizeInBits()
return "(_ BitVec " + size + ")";
}
@Override
public String caseTypeList(TypeList type) {
// (Array indexType valueType)
return "(Array (_ BitVec 32) " + doSwitch(type.getType()) + ")";
}
@Override
public String caseTypeString(TypeString type) {
int size = 32; // type.getSizeInBits()
return "(_ BitVec " + size + ")";
}
@Override
public String caseTypeUint(TypeUint type) {
int size = 32; // type.getSizeInBits()
return "(_ BitVec " + size + ")";
}
}
private Actor actor;
private boolean hasFailed;
public GuardSatChecker(Actor actor) {
this.actor = actor;
}
/**
* Returns <code>true</code> if the guards of action1 and action2 are
* compatible, and <code>false</code> if they are mutually exclusive.
*
* @param action1
* action 1
* @param action2
* action 2
* @return <code>true</code> if the guards of action1 and action2 are
* compatible
*/
public boolean checkSat(Action action1, Action action2) {
SmtTranslator translator = new SmtTranslator();
for (Port port : actor.getInputs()) {
translator.doSwitch(port);
}
translator.doSwitch(action1);
translator.doSwitch(action2);
SmtScript script = translator.getScript();
// check whether the guards are compatible or not
script.addCommand("(assert (and " + action1.getScheduler().getName()
+ " " + action2.getScheduler().getName() + "))");
script.addCommand("(check-sat)");
hasFailed = false;
if (!translator.hasFailed()) {
SmtSolver solver = new SmtSolver(actor);
boolean result = solver.checkSat(script);
hasFailed = solver.hasFailed();
// for SmtTranslator debugging, print script.getCommands() here
return result;
} else {
hasFailed = true;
}
return false;
}
/**
* Returns <code>true</code> if the guard of action can be satisfied and
* <code>false</code> if the guard can never be satisfied.
*
* @param action
* action
* @return <code>true</code> if the guards of action can be satisfied
*/
public boolean checkSat(Action action) {
SmtTranslator translator = new SmtTranslator();
for (Port port : actor.getInputs()) {
translator.doSwitch(port);
}
translator.doSwitch(action);
SmtScript script = translator.getScript();
// check whether the guard is satisfiable or not
script.addCommand("(assert " + action.getScheduler().getName() + ")");
script.addCommand("(check-sat)");
hasFailed = false;
if (!translator.hasFailed()) {
SmtSolver solver = new SmtSolver(actor);
boolean result = solver.checkSat(script);
hasFailed = solver.hasFailed();
// for SmtTranslator debugging, print script.getCommands() here
return result;
} else {
hasFailed = true;
}
return false;
}
/**
* Computes the values of tokens that when present on the given ports
* satisfy the following two conditions:
* <ol>
* <li>no action in <code>others</code> can be fired</li>
* <li>the given action can be fired</li>
* </ol>
*
* @param ports
* a list of ports
* @param others
* a list of actions
* @param action
* an action
* @return a map that associate ports with values so that when token are
* peeked on ports in the map, values associated with them are given
* to the interpreter, which allows <code>action</code> to fire
*/
public Map<String, Object> computeTokenValues(List<Port> ports,
List<Action> others, Action action) {
SmtTranslator translator = new SmtTranslator();
for (Port port : actor.getInputs()) {
translator.doSwitch(port);
}
translator.doSwitch(action);
SExp assertion = new SExpSymbol(action.getScheduler().getName());
ListIterator<Action> it = others.listIterator(others.size());
while (it.hasPrevious()) {
Action previous = it.previous();
translator.doSwitch(previous);
assertion = new SExpList(new SExpSymbol("ite"), new SExpSymbol(
previous.getScheduler().getName()),
new SExpSymbol("false"), assertion);
}
SmtScript script = translator.getScript();
// check whether the guards are compatible or not
SExpList sexpAssert = new SExpList(new SExpSymbol("assert"), assertion);
script.addCommand(sexpAssert.toString());
script.addCommand("(check-sat)");
SmtSolver solver = new SmtSolver(actor);
solver.checkSat(script, action, ports);
// fills the map
return solver.getAssertions();
}
public boolean hasFailed() {
return hasFailed;
}
}