/*
* xtc - The eXTensible Compiler
* Copyright (C) 2009-2012 New York University
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.lang.cpp;
import java.io.Writer;
import java.io.StringWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import xtc.lang.cpp.Syntax.Kind;
import net.sf.javabdd.BDDFactory;
import net.sf.javabdd.BDD;
/** Presence condition manager. It abstracts away the nitty-gritty of
* using BDDs.
*
* @author Paul Gazzillo
* @version $Revision: 1.14 $
*/
class PresenceConditionManager {
/** The BDD factory. */
private BDDFactory B;
/** The variable name manager. */
private Variables vars;
/** The stack of nested presence conditions. */
private LinkedList<BDD> stack;
/**
* The current global presence condition, i.e. the conjunction of
* nested presence conditions.
*/
private BDD global;
/**
* The local presence condition of the current conditional branch.
*/
private BDD branch;
/**
* The not of the union of all previous conditional branches. This
* models the short-circuiting of preprocessor conditonals.
*/
private BDD notBranches;
/** The current presence condition. */
private PresenceCondition current;
/** The CONFIG_-only domain of variables. */
private BDD configDomain;
/**
* Create a new presence condition manager.
*/
public PresenceConditionManager() {
this.B = BDDFactory.init(5000000, 50000);
this.vars = new Variables(B);
this.stack = new LinkedList<BDD>();
this.global = B.one();
this.branch = null;
this.notBranches = null;
this.current = null;
this.configDomain = null;
}
/**
* Copy a presence condition manager.
*
* @param The presence condition manager to copy.
*/
public PresenceConditionManager(PresenceConditionManager presenceConditionManager) {
this.B = presenceConditionManager.B;
this.vars = presenceConditionManager.vars;
this.stack = new LinkedList<BDD>();
for (BDD bdd : presenceConditionManager.stack) {
if (bdd != null) {
this.stack.add(bdd.id());
}
else {
this.stack.add(null);
}
}
this.global = presenceConditionManager.global.id();
if (null != presenceConditionManager.branch) {
this.branch = presenceConditionManager.branch.id();
}
else {
this.branch = null;
}
if (null != presenceConditionManager.notBranches) {
this.notBranches = presenceConditionManager.notBranches.id();
}
else {
this.notBranches = null;
}
this.current = null;
}
/**
* Free all BDDs held by this presence condition manager. Note this does not
* free the BDDs generated by it, but only those it uses to track
* nested presence conditions.
*/
public void free() {
global.free();
if (null != branch) {
branch.free();
}
if (null != notBranches) {
notBranches.free();
}
for (BDD bdd : stack) {
if (null != bdd) {
bdd.free();
}
}
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Push the current presence condition onto the stack.
*/
public void push() {
stack.push(notBranches);
stack.push(branch);
stack.push(global);
notBranches = B.one();
branch = B.zero();
global = B.zero();
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Enters a new local presence condition.
*
* @param bdd The new local presence condition. Will be delRef'ed.
*/
public void enter(BDD bdd) {
notBranches.andWith(branch.not());
branch.free();
branch = bdd;
global.free();
// peekGlobal && notBranches && branch
global = stack.peek().and(notBranches).andWith(branch.id());
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Enter a NEXT conditional branch. IMPORTANT: the preprocessor
* needs to call enterElse before evaluating the #elif's conditional
* expression, otherwise macros in the expression will be evaluated
* as if they were in the previous branch, which is wrong.
*
* @param bdd The presence condition of the branch.
*/
public void enterElif(BDD bdd) {
branch = bdd;
global = stack.peek().and(notBranches).andWith(branch.id());
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Enter an else branch. Else is a branch whose local presence condition is
* TRUE.
*/
public void enterElse() {
enter(B.one());
}
/**
* Pop the current presence condition off of the stack.
*/
public void pop() {
global.free();
branch.free();
notBranches.free();
global = stack.pop();
branch = stack.pop();
notBranches = stack.pop();
if (null != current) {
current.delRef();
current = null;
}
}
/**
* Get the parent presence condition, i.e. the presence condition
* containing the current one.
*
* @param The parent presence condition.
*/
public PresenceCondition parent() {
return new PresenceCondition(stack.peek().id());
}
/**
* Whether the current presence condition is true.
*
* @return true When the the current presence condition is true.
*/
public boolean isTrue() {
return global.isOne();
}
/**
* Whether the current presence condition is false
*
* @return true When the the current presence condition is false.
*/
public boolean isFalse() {
return global.isZero();
}
/**
* Get the current presence condition.
*
* @return The current presence condition.
*/
public PresenceCondition reference() {
if (null == current) {
current = new PresenceCondition(global.id());
}
current.addRef();
return current;
}
public boolean is(PresenceCondition presenceCondition) {
return global.equals(presenceCondition.getBDD());
}
/**
* Collect the domain of all CONFIG_-related BDDs. This domain is
* used by simplifyToConfigs. Calling this method will regenerate
* the domain if it's already computed.
*/
public void generateConfigDomain() {
if (null != this.configDomain) this.configDomain.free();
this.configDomain = B.zero();
for (int i = 0; i < getVariableManager().getSize(); i++) {
if (getVariableManager().getName(i).startsWith("(defined CONFIG_")) {
this.configDomain = this.configDomain.orWith(getVariableManager().getVariable(getVariableManager().getName(i)));
}
}
}
/**
* Check whether the config domain has been computed yet.
*
* @return true if the domain has been computed.
*/
public boolean hasConfigDomain() {
return null != this.getConfigDomain();
}
/**
* Get the CONFIG_-only domain.
*
* @return The config domain
*/
public BDD getConfigDomain() {
return this.configDomain;
}
/**
* Get the CONFIG_-only domain.
*
* @return The config domain
*/
public PresenceCondition getConfigDomainCond() {
if (! hasConfigDomain()) {
generateConfigDomain();
}
return new PresenceCondition(getConfigDomain());
}
/**
* Simplify the BDD by narrowing the domain to only CONFIG_
* variables. This will call generateConfigDomain only if the
* domain does not exist already. Call generateConfigDomain to
* regenerate if new variables have been added.
*
* @param pc The presence condition to simplify.
* @return The simplified BDD.
*/
public PresenceCondition simplifyToConfigs(PresenceCondition pc) {
if (! hasConfigDomain()) {
generateConfigDomain();
}
BDD r = pc.getBDD().id();
for (int i = 0; i < getVariableManager().getSize(); i++) {
if (getVariableManager().getName(i).contains("CONFIG_")) {
r = r.simplify(getVariableManager().getVariable(getVariableManager().getName(i)));
}
}
return new PresenceCondition(r);
// return new PresenceCondition(pc.getBDD().simplify(getConfigDomain()));
}
public PresenceCondition getRestrictCond(boolean val) {
BDD restrictBDD = B.one();
for (int i = 0; i < getVariableManager().getSize(); i++) {
// if (! getVariableManager().getName(i).contains("(defined CONFIG_")) {
if (! getVariableManager().getName(i).contains("CONFIG_")) {
if (val) {
restrictBDD = restrictBDD.andWith(getVariableManager().getVariable(getVariableManager().getName(i)));
} else {
BDD varbdd = getVariableManager().getVariable(getVariableManager().getName(i));
restrictBDD = restrictBDD.andWith(varbdd.not());
varbdd.free();
}
}
}
return new PresenceCondition(restrictBDD);
}
public Variables getVariableManager() {
return vars;
}
/**
* The nesting depth of presence conditions.
*
* @return The nesting depth.
*/
public int getDepth() {
return stack.size();
}
/**
* The BDD factory used to create BDDs. This is needed for directly
* manipulating BDDs outside of the PresenceConditionManager, because all BDDs
* that share variables need to be created from the same factory.
*
* @return The BDD factory.
*/
public BDDFactory getBDDFactory() {
return B;
}
/**
* Return a new presence condition with the disjunction of all
* variables, turned off or on according to the parameters.
*
* @param defaultSetting true for all variables on, false for off.
* @param exceptions null for none, list of strings to set opposite
* the defaultSetting.
* @return The new presence condition.
*/
public BDD evaluateBDDs(ConditionEvaluator evaluator) {
BDD b = B.one();
for (int i = 0; i < vars.indices.size(); i++) {
String varString = vars.indices.get(i);
final CLexer clexer = new CLexer(new StringReader(varString));
clexer.setFileName("string expression");
Iterator<Syntax> stream = new Iterator<Syntax>() {
Syntax syntax;
public Syntax next() {
try {
syntax = clexer.yylex();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
return syntax;
}
public boolean hasNext() {
return syntax.kind() != Kind.EOF;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
stream.next();
BDD result = evaluator.evaluate(stream);
if (result.isOne()) {
b.andWith(B.ithVar(i));
} else if (result.isZero()) {
BDD ith = B.ithVar(i);
BDD not = ith.not();
ith.free();
b.andWith(not);
} else {
System.err.println("unresolved BDD variable");
System.err.println(varString);
BDD ith = B.ithVar(i);
BDD not = ith.not();
ith.free();
b.andWith(not);
// System.exit(1);
}
}
return b;
}
/**
* Return a new presence condition with the disjunction of all
* variables, turned off or on according to the parameters.
*
* @param defaultSetting true for all variables on, false for off.
* @param exceptions null for none, list of strings to set opposite
* the defaultSetting.
* @return The new presence condition.
*/
public BDD createConfiguration(boolean defaultSetting,
List<String> exceptions) {
BDD b = B.one();
for (int i = 0; i < vars.indices.size(); i++) {
boolean setting = defaultSetting;
String varString = vars.indices.get(i);
if (null != exceptions && exceptions.contains(varString)) {
setting = ! setting;
}
if (setting) {
b.andWith(B.ithVar(i));
} else {
BDD ith = B.ithVar(i);
BDD not = ith.not();
ith.free();
b.andWith(not);
}
}
return b;
}
/**
* Return a new presence condition with the disjunction of all
* variables.
*
* @return The new presence condition.
*/
public BDD allYes() {
return createConfiguration(true, null);
}
/**
* Return a new presence condition with the disjunction of all
* variables negated.
*
* @return The new presence condition.
*/
public BDD allNo() {
return createConfiguration(false, null);
}
/**
* Print one sat to a writer.
*
* @param writer The writer.
* @throws IOException Because it uses a Writer.
*/
public void printOneSat(byte[] sat, Writer writer) throws IOException {
boolean first = true;
for (int i = 0; i < sat.length; i++) {
if (sat[i] >= 0 && ! first) {
writer.write(" && ");
}
switch (sat[i]) {
case 0:
writer.write("!");
case 1:
writer.write(vars.getName(i));
first = false;
break;
}
}
}
/**
* Print the BDD to a writer.
*
* @param bdd The bdd.
* @param writer The writer.
* @throws IOException Because it uses a Writer.
*/
public void printBDD(BDD bdd, Writer writer) throws IOException {
List allsat;
boolean firstTerm;
if (bdd.isOne()) {
writer.write("1");
return;
} else if (bdd.isZero()) {
writer.write("0");
return;
}
allsat = (List) bdd.allsat();
firstTerm = true;
for (Object o : allsat) {
if (! firstTerm) {
writer.write(" || ");
}
firstTerm = false;
printOneSat((byte[]) o, writer);
}
}
/**
* Get the set of all config vars used by the given BDD.
*
* @param The BDD.
* @return The set of all config vars used.
*/
public Set<String> getAllConfigsFromBDD(BDD bdd) {
List allsat;
Set<String> allConfigs = new HashSet<String>();
if (bdd.isOne()) {
return allConfigs;
} else if (bdd.isZero()) {
return allConfigs;
}
allsat = (List) bdd.allsat();
for (Object o : allsat) {
byte[] sat;
sat = (byte[]) o;
for (int i = 0; i < sat.length; i++) {
switch (sat[i]) {
case 0:
allConfigs.add("!" + vars.getName(i));
break;
case 1:
allConfigs.add(vars.getName(i));
break;
}
}
}
return allConfigs;
}
/** A reference-counted presence condition that automatically cleans up BDD when
* nothing references it anymore.
*/
public class PresenceCondition {
private BDD bdd;
private int refs;
/** Creates a new PresenceCondition out of the given bdd. Make sure the bdd
* is not shared by anyone else.
*/
public PresenceCondition(BDD bdd) {
this.bdd = bdd;
this.refs = 1;
}
public PresenceCondition(boolean value) {
this.bdd = value ? B.one() : B.zero();
this.refs = 1;
}
public boolean isTrue() {
return bdd.isOne();
}
public boolean isFalse() {
return bdd.isZero();
}
/** Return the negated presence condition. */
public PresenceCondition not() {
return new PresenceCondition(bdd.not());
}
/** Return this presence condition and c. Free any intermediate bdds. */
public PresenceCondition and(PresenceCondition c) {
return new PresenceCondition(bdd.and(c.bdd));
}
/** Return this presence condition and not c. Free any intermediate bdds. */
public PresenceCondition andNot(PresenceCondition c) {
PresenceCondition newPresenceCondition;
BDD notBDD;
notBDD = c.bdd.not();
newPresenceCondition = new PresenceCondition(bdd.and(notBDD));
notBDD.free();
return newPresenceCondition;
}
/** Return this presence condition or c. Free any intermediate bdds. */
public PresenceCondition or(PresenceCondition c) {
return new PresenceCondition(bdd.or(c.bdd));
}
/** Restrict */
public PresenceCondition restrict(PresenceCondition c) {
return new PresenceCondition(bdd.restrict(c.getBDD()));
}
/** Simplify */
public PresenceCondition simplify(PresenceCondition c) {
return new PresenceCondition(bdd.simplify(c.getBDD()));
}
/** One sat */
public PresenceCondition satOne() {
return new PresenceCondition(bdd.satOne());
}
/** All sats */
public void allsat() {
bdd.allsat();
}
/** Compare */
public boolean is(PresenceCondition presenceCondition) {
return is(presenceCondition.getBDD());
}
/** Compare */
public boolean is(BDD bdd) {
return this.bdd.equals(bdd);
}
/**
*
*/
public boolean isMutuallyExclusive(PresenceCondition presenceCondition) {
PresenceCondition and;
and = this.and(presenceCondition);
if (and.isFalse()) {
and.delRef();
return true;
}
else {
and.delRef();
return false;
}
}
public PresenceCondition addRef() {
if (refs > 0) {
refs++;
}
return this;
}
public void delRef() {
if (refs > 0) {
refs--;
if (0 == refs) {
bdd.free();
}
}
}
/**
* Get the raw BDD backing this presence condition.
*
* @return The raw BDD.
*/
public BDD getBDD() {
return bdd;
}
/**
* Print the BDD to a writer.
*
* @param bdd The BDD.
* @param writer The writer.
* @throws IOException Because it uses a Writer.
*/
public void print(Writer writer) throws IOException {
printBDD(bdd, writer);
}
// /**
// * Print the BDD as a CNF clauses.
// *
// * @param writer The writer.
// * @throws IOException Because it uses a Writer.
// */
// public void printCNF(Writer writer) throws IOException {
// if (this.isTrue()) {
// writer.write("1");
// return;
// } else if (this.isFalse()) {
// writer.write("0");
// return;
// }
// // We use the allsat() function on the bdd to get the clauses.
// // allsat is in DNF, so we first negate the bdd. Then, to
// // generate CNF, we negate the clauses to make them conjunctive
// // again.
// PresenceCondition not = this.not();
// List allsat = (List) not.getBDD().allsat();
// for (Object o : allsat) {
// byte[] sat = (byte[]) o;
// ArrayList<Integer> clause = new ArrayList<Integer>();
// StringBuilder sb = new StringBuilder();
// for (int i = 0; i < sat.length; i++) {
// int sign = 1;
// switch (sat[i]) {
// case 1:
// // negate again
// sign = -1;
// case 0:
// String varname = not.presenceConditionManager().getVariableManager().getName(i);
// // if (varname.startsWith("(defined ")) {
// if (varname.contains("CONFIG")) {
// // if (varname.startsWith("(defined CONFIG_")) {
// // varname = varname.substring(9, varname.length() - 1);
// if (-1 == sign) {
// sb.append("-");
// }
// // sb.append("[");
// sb.append(varname);
// // sb.append("]");
// sb.append(",");
// }
// break;
// }
// }
// if (sb.toString().length() > 0) {
// writer.write("(");
// writer.write(sb.toString());
// writer.write(")");
// }
// }
// not.delRef();
// }
/**
* Print the BDD as a CNF clauses.
*
* @param writer The writer.
* @throws IOException Because it uses a Writer.
*/
public void printNotCNF(PresenceCondition cond, Writer writer) throws IOException {
if (cond.isTrue()) {
writer.write("0");
return;
} else if (cond.isFalse()) {
writer.write("1");
return;
}
// We use the allsat() function on the bdd to get the clauses.
// allsat is in DNF, so we first negate the bdd. Then, to
// generate CNF, we negate the clauses to make them conjunctive
// again.
List allsat = (List) cond.getBDD().allsat();
for (Object o : allsat) {
byte[] sat = (byte[]) o;
ArrayList<Integer> clause = new ArrayList<Integer>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < sat.length; i++) {
int sign = 1;
switch (sat[i]) {
case 1:
// negate again
sign = -1;
case 0:
String varname = cond.presenceConditionManager().getVariableManager().getName(i);
// if (varname.startsWith("(defined ")) {
if (varname.contains("CONFIG")) {
// if (varname.startsWith("(defined CONFIG_")) {
// varname = varname.substring(9, varname.length() - 1);
if (-1 == sign) {
sb.append("-");
}
// sb.append("[");
sb.append(varname);
// sb.append("]");
sb.append(",");
}
break;
}
}
if (sb.toString().length() > 0) {
writer.write("(");
writer.write(sb.toString());
writer.write(")");
}
}
}
/**
* Get the set of all config vars used.
*
* @return The set of all config vars used.
*/
public Set<String> getAllConfigs() {
return getAllConfigsFromBDD(bdd);
}
public PresenceConditionManager presenceConditionManager() {
return PresenceConditionManager.this;
}
/** Output the presence condition as a valid cpp conditional expression */
public String toCNF() {
StringWriter writer = new StringWriter();
try {
PresenceCondition not = this.not();
printNotCNF(not, writer);
not.delRef();
} catch (IOException e) {
// An inelegant way to sidestep not being able to throw an
// exception from the overridden toString method.
throw new RuntimeException();
}
return writer.toString();
}
/** Output the presence condition as a valid cpp conditional expression */
public String toNotCNF() {
StringWriter writer = new StringWriter();
try {
printNotCNF(this, writer);
} catch (IOException e) {
// An inelegant way to sidestep not being able to throw an
// exception from the overridden toString method.
throw new RuntimeException();
}
return writer.toString();
}
/** Output the presence condition as a valid cpp conditional expression */
public String toString() {
StringWriter writer = new StringWriter();
try {
print(writer);
} catch (IOException e) {
// An inelegant way to sidestep not being able to throw an
// exception from the overridden toString method.
throw new RuntimeException();
}
return writer.toString();
}
}
/**
* Manages BDD variables and provides a string representation for
* BDD variables.
*/
public static class Variables {
/** The BDD factory */
private BDDFactory B;
/** The initial number of variables */
private int varNum;
/** The number of variables to add when varNum is topped */
private int extVarNum;
/** Hash from variable name to BDD variable index */
private Map<String, BDD> variables;
/** Map from BDD variable index to variable name */
private List<String> indices;
/**
* Create a new BDD variable manager.
*
* @param B The BDD factory.
*/
public Variables(BDDFactory B) {
this(B, 1023, 512);
}
/**
* Create a new BDD variable manager.
*
* @param B The BDD factory.
* @param varNum The initial number of BDD variables.
* @param extVarNum How much to increase the number of variables
* by each time the limit is reached.
*/
public Variables(BDDFactory B, int varNum, int extVarNum) {
this.B = B;
this.varNum = varNum;
this.extVarNum = extVarNum;
this.variables = new HashMap<String, BDD>();
this.indices = new ArrayList<String>();
B.setVarNum(this.varNum);
B.setMinFreeNodes(.40);
B.setMaxIncrease(500000);
}
/**
* Create a new BDD with the given variable name. If the variable
* does not exist yet, create it.
*
* @param str The name of the variable.
* @return A new BDD containing the variable.
*/
public BDD getVariable(String str) {
if (variables.containsKey(str)) {
return variables.get(str).id();
} else {
int newNum = indices.size();
BDD newBDD;
if (newNum > varNum - 1) {
varNum += extVarNum;
//System.err.println("INCREASE: " + varNum);
B.extVarNum(extVarNum);
}
newBDD = B.ithVar(newNum);
variables.put(str, newBDD);
indices.add(str);
return newBDD.id();
}
}
/**
* Get the number of variables.
*
* @return The number of variables.
*/
public int getSize() {
return indices.size();
}
/**
* Map BDD variable number to name.
*
* @param i The variable number.
* @return The variable name.
*/
public String getName(int i) {
if (i < indices.size()) {
return indices.get(i);
}
else {
return null;
}
}
/**
* Determines whether the variable name exists.
*
* @param The variable name.
* @return true if it exists.
*/
public boolean hasVariable(String name) {
return variables.containsKey(name);
}
/**
* Syntactic sugar for hasVariable(createDefinedVariable(name)).
*
* @param name The macro name.
* @return true If the variable exists.
*/
public boolean hasDefinedVariable(String name) {
return hasVariable(createDefinedVariable(name));
}
/**
* Syntactic sugar for getVariable(createDefinedVariable(name)).
*
* @param name The macro name.
* @return A new BDD containing the variable.
*/
public BDD getDefinedVariable(String name) {
return getVariable(createDefinedVariable(name));
}
/**
* Create a string representation for the "is a macro defined"
* boolean variable, i.e. the "defined" operator. This variable is
* truly boolean variable, as opposed to the macro name itself,
* since a macro may have non-boolean values.
*
* @param The name of the macro.
* @return A string representation of the boolean variable.
*/
public static String createDefinedVariable(String name) {
return "(defined " + name + ")";
}
/**
* Create a string representation for the "is a macro not defined"
* boolean variable
*
* @param The name of the macro.
* @return A string representation of the boolean variable.
*/
public static String createNotDefinedVariable(String name) {
return "! " + createDefinedVariable(name);
}
}
}