/* * xtc - The eXTensible Compiler * Copyright (C) 2009-2011 New York University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; 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.util.LinkedList; import java.util.List; import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import net.sf.javabdd.BDDFactory; import net.sf.javabdd.BDD; /** Conditional context manager. It abstracts away the nitty-gritty * of using the BDD objects. * * @author Paul Gazzillo * @version $Revision: 1.39 $ */ class ContextManager { /** 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 context */ private Context current; /** * Create a new context manager. */ public ContextManager() { 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; } /** * Copy a context manager. * * @param The context manager to copy. */ public ContextManager(ContextManager contextManager) { this.B = contextManager.B; this.vars = contextManager.vars; this.stack = new LinkedList<BDD>(); for (BDD bdd : contextManager.stack) { if (bdd != null) { this.stack.add(bdd.id()); } else { this.stack.add(null); } } this.global = contextManager.global.id(); if (null != contextManager.branch) { this.branch = contextManager.branch.id(); } else { this.branch = null; } if (null != contextManager.notBranches) { this.notBranches = contextManager.notBranches.id(); } else { this.notBranches = null; } this.current = null; } /** * Free all BDDs held by this context 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 context. * * @param bdd The new local context. 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 context 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 Context parent() { return new Context(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 Context reference() { if (null == current) { current = new Context(global.id()); } current.addRef(); return current; } public boolean is(Context context) { return global.equals(context.getBDD()); } 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 ContextManager, because all BDDs * that share variables need to be created from the same factory. * * @return The BDD factory. */ public BDDFactory getBDDFactory() { return B; } /** A reference-counted context that automatically cleans up BDD when * nothing references it anymore. */ public class Context { private BDD bdd; private int refs; /** Creates a new Context out of the given bdd. Make sure the bdd * is not shared by anyone else. */ public Context(BDD bdd) { this.bdd = bdd; this.refs = 1; } public Context(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 context. */ public Context not() { return new Context(bdd.not()); } /** Return this context and c. Free any intermediate bdds. */ public Context and(Context c) { return new Context(bdd.and(c.bdd)); } /** Return this context and not c. Free any intermediate bdds. */ public Context andNot(Context c) { Context newContext; BDD notBDD; notBDD = c.bdd.not(); newContext = new Context(bdd.and(notBDD)); notBDD.free(); return newContext; } /** Return this context or c. Free any intermediate bdds. */ public Context or(Context c) { return new Context(bdd.or(c.bdd)); } /** Restrict */ public Context restrict(Context c) { return new Context(bdd.restrict(c.getBDD())); } /** Compare */ public boolean is(Context context) { return is(context.getBDD()); } /** Compare */ public boolean is(BDD bdd) { return this.bdd.equals(bdd); } /** * */ public boolean isMutuallyExclusive(Context context) { Context and; and = this.and(context); if (and.isFalse()) { and.delRef(); return true; } else { and.delRef(); return false; } } public Context 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 writer The writer. * @throws IOException Because it uses a Writer. */ public void print(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) { byte[] sat; boolean first; if (! firstTerm) { writer.write(" || "); } firstTerm = false; sat = (byte[]) o; 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; } } } } /** Output the context 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(); } } /** * 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); } /** * 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 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 String createNotDefinedVariable(String name) { return "! " + createDefinedVariable(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)); } } }