/* * This file is part of the X10 project (http://x10-lang.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.opensource.org/licenses/eclipse-1.0.php * * (C) Copyright IBM Corporation 2006-2010. */ package x10.constraint.xnative; import x10.constraint.xnative.XAnd; import x10.constraint.XConstraint; import x10.constraint.xnative.XDisEquals; import x10.constraint.xnative.XEquals; import x10.constraint.XFailure; import x10.constraint.XField; import x10.constraint.XFormula; import x10.constraint.XTerm; import x10.constraint.xnative.XNativeTerm; import x10.constraint.XVar; import x10.constraint.visitors.AddInVisitor; import x10.constraint.visitors.ConstraintGenerator; import x10.constraint.visitors.EntailsVisitor; import x10.constraint.visitors.XGraphVisitor; import x10.util.CollectionFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.LinkedList; import java.util.Map; import java.util.Set; /** * * A constraint solver for the following constraint system. Note terms in this * constraint system are untyped. * <pre> * t ::= x -- variable * | t.f -- field access * | g(t1,..., tn) -- uninterpreted function symbol * * c ::= t == t -- equality * | t != t -- dis-equality * | c,c -- conjunction * | p(t1,..., tn) -- atomic formula * </pre> * * The constraint system implements the usual congruence rules for equality. * That is, if <code>s1,...,sn</code> and * <code>t1,...,tn</code> are terms, and <code> s1 == t1,..., sn == tn</code>, * then * <code>g(s1,..., sn) == g(s1,...,sn)</code>, and * <code>p(t1,..., tn) == p(t1,...,tn)</code>. Further, * <uline> * <li> <code>s equals t</code> implies <code>t equals s</code> * <li> <code>s equals t</code> and <code>t equals u</code> implies <code> s * equals u</code> * <li> it is always the case that <code>s equals s</code> * </uline> * <p>Terms are created using the static API in XNativeTerms. The <code>==</code> * relation on terms at the level of the constraint system is translated into * the <code>equals</code> relation on the Java representation of the terms. * * <p>A constraint is implemented as a graph whose nodes are XPromises. Two * different constraints will not share @link{XPromise}. See the description of * @link{XPromise} for more information about the structure of a promise. * * <p>This representation is a bit different from the Nelson-Oppen and * Shostak congruence closure algorithms described, e.g. in Cyrluk, Lincoln and * Shankar "On Shostak's Decision Procedure for Combination of Theories", * CADE 96. * * <p> * <bf>TODO:</bf> * Use Shostak's congruence procedure. Treat <tt>t.f</tt> as the term * <tt>f(t)</tt>, i.e. each field is regarded as a unary function symbol. * This will be helpful in implementing Nelson-Oppen integration of * decision procedures. * * <p> Additional Notes. * This constraint system and its implementation knows nothing about X10 or * internal compiler structures. Specifically it knows nothing about X10 types. * The package x10.types.constraints contains an extension of this type system * that is aware of a this variable, a self variable and other compiler-related * data-structures. * * @author vj * */ public class XNativeConstraint implements Cloneable, XConstraint { protected Map<XNativeTerm, XPromise> roots; protected boolean consistent = true; protected boolean valid = true; public XNativeConstraint() {} public Map<XNativeTerm, XPromise> roots() { return roots;} /** * Return the list of existentially quantified variables in this constraint. * @return */ public List<XNativeVar> eqvs() { List<XNativeVar> xvars = new LinkedList<XNativeVar>(); if (roots==null) return xvars; for (XNativeTerm xt : roots.keySet()) { if (xt.isEQV()) xvars.add((XNativeVar) xt); } return xvars; } /** * Return the set of terms occurring in this constraint. * @return */ public Set<XNativeTerm> rootTerms() { return roots == null ? Collections.<XNativeTerm> emptySet() : roots.keySet(); } /* private void addTerm(XNativeTerm term, Set<XVar> result) { if (term==null) return; if (term instanceof XFormula) { XFormula form = (XFormula) term; for (XNativeTerm arg : form.arguments()) addTerm(arg, result); return; } if (term instanceof XVar) addVar((XVar) term, result); } private void addVar(XVar var, Set<XVar> result) { if (var == null) return; result.add(var); if (var instanceof XField) { addVar(((XField)var).receiver(), result); } } public Set<XVar> vars() { List<XNativeTerm> terms = constraints(); Set <XVar> result = CollectionFactory.newHashSet(); for (XNativeTerm term : terms) { addTerm(term, result); } return result; } */ /** * Copy this constraint. Implemented via a graph traversal. */ public XNativeConstraint copy() { XNativeConstraint result = new XNativeConstraint(); result.addIn(this); return result; // return copyInto(new XNativeConstraint()); } public void addIn(XConstraint c) { if (c== null) return; if (! c.consistent()) { setInconsistent(); return; } if (c.valid()) return; AddInVisitor v = new AddInVisitor(true, false, this); // hideFake==false to permit the "place checking" to work. // This ensures that multiple objects created at the same place // e.g. GlobalRef's, are treated as being at the same place by the // type-checker. c.visit(v); // vj: What about thisVar for c? Should that be added? // thisVar = getThisVar(this, c); return; } /** * Return the result of copying this into c. * TODO: Do this with an XGraphVisitor. * @param c * @return */ /* protected <T extends XNativeConstraint> T copyInto(T c) { c.consistent = consistent; c.valid = valid; if (roots !=null) { c.roots = CollectionFactory.<XNativeTerm, XPromise>newHashMap(roots.size()); Map<XPromise, XPromise> redirects = CollectionFactory.<XPromise, XPromise>newHashMap(roots.size()); for (Map.Entry<XNativeTerm, XPromise> entry : roots.entrySet()) { XNativeTerm xt = entry.getKey(); XPromise p = entry.getValue(); XPromise np = p.cloneShallow(); c.roots.put(xt, np); redirects.put(p, np); } for (XPromise p : roots.values()) { p.transfer(redirects); } } return c; } */ /** * Return the term v is bound to in this constraint, and null * if there is no such term. This term will be distinct from v. * */ public XVar bindingForVar(XVar v) { XPromise p = ((XNativeVar)v).nfp(this); if (p != null && p.term() instanceof XVar && ! p.term().equals(v)) { return (XNativeVar)p.term().nf(this); } return null; } protected XNativeTerm bindingForRootField(XVar root, Object field) { XNativeTerm term = new XNativeField<Object>(root, field); XPromise p = term.nfp(this); if (p == null) return null; return p.term(); } /** * Return the list of atoms (atomic formulas) in this constraint. * @return */ public List<XNativeFormula<?>> atoms() { List<XNativeFormula<?>> r = new ArrayList<XNativeFormula<?>>(5); if (roots == null) return r; for (XNativeTerm t : roots.keySet()) { if (t instanceof XNativeFormula<?>) { r.add((XNativeFormula<?>)t); } } return r; } /** * Return the set of XVars mentioned in this constraint. * @return */ public Set<XNativeVar> vars() { Set <XNativeVar> result = CollectionFactory.newHashSet(); for (XTerm term : constraints()) addTerm((XNativeTerm)term, result); return result; } private void addTerm(XNativeTerm term, Set<XNativeVar> result) { if (term==null) return; if (term instanceof XNativeFormula) { XNativeFormula<?> form = (XNativeFormula<?>) term; for (XNativeTerm arg : form.arguments()) addTerm(arg, result); return; } if (term instanceof XVar) addVar((XNativeVar) term, result); } private void addVar(XNativeVar var, Set<XNativeVar> result) { if (var == null) return; result.add(var); if (var instanceof XField) addVar(((XNativeField<?>)var).receiver, result); } /** * Is the constraint consistent? That is, does it have a solution? * Implementation Note: In the body of XNativeConstraint, we build in that consistent() * returns the field consistent. * @return true iff the constraint is consistent. */ public boolean consistent() { return consistent; } /** Is the constraint valid? i.e. is it satisfied by every solution? * */ public boolean valid() { return consistent && valid;} private boolean flatten(boolean isEq, XNativeTerm left, XNativeTerm right) { // handle several special cases: // (X==Y)==true ---> X==Y // (X!=Y)==true ---> X!=Y // (X==Y)==false ---> X!=Y // (X!=Y)==false ---> X==Y XNativeTerm boolLit = left.isBoolean() ? left : right.isBoolean() ? right : null; if (boolLit!=null) { XNativeTerm other = boolLit==left ? right : left; boolean isEquals = other instanceof XEquals; boolean isDisEquals = other instanceof XDisEquals; if (isEquals || isDisEquals) { XNativeFormula<?> formula = (XNativeFormula<?>) other; XNativeTerm[] args = formula.arguments(); assert args.length==2; left = args[0]; right = args[1]; boolean isLitTrue = boolLit==XNativeLit.TRUE; if (isLitTrue==isEquals) {} // ok else isEq = !isEq; if (isEq) addBinding(left, right); else addDisBinding(left, right); return true; } } return false; } /** * Add t1=t2 to the constraint, unless it is inconsistent. * Note: constraint is modified in place. * @param nodeLabel -- t1 * @param val -- t2 */ public void addBinding(XTerm l, XTerm r) { assert l != null; assert r != null; XNativeTerm left = (XNativeTerm) l; XNativeTerm right = (XNativeTerm) r; if (flatten(true,left, right)) return; if (!consistent) return; if (roots == null) roots = CollectionFactory.<XNativeTerm, XPromise> newHashMap(); XPromise p1 = intern(left); if (p1==null) { setInconsistent(); return;} XPromise p2 = intern(right); if (p2 == null) { setInconsistent();return;} try { valid &= ! p1.bind(p2, this); } catch (XFailure z) { setInconsistent(); } } /** * Add t1 != t2 to the constraint. * Note: Constraint is updated in place. * @param nodeLabel * @param t */ public void addDisBinding(XTerm l, XTerm r) { assert l != null; assert r != null; XNativeTerm left = (XNativeTerm) l; XNativeTerm right = (XNativeTerm) r; if (flatten(false,left, right)) return; if (! consistent) return; if (roots == null) roots = CollectionFactory.<XNativeTerm,XPromise> newHashMap(); XPromise p1 = intern(left); if (p1 == null) {setInconsistent();return;} XPromise p2 = intern(right); if (p2 == null) {setInconsistent();return;} if (p1.equals(p2)) {setInconsistent();return;} try { valid &= ! p1.disBind(p2); if (left instanceof XField<?> && right instanceof XField<?>) { XField<?> lf = (XField<?>) left, rf = (XField<?>) right; if (lf.field()==rf.field()) addDisBinding(lf.receiver(), rf.receiver()); } } catch (XFailure z) { setInconsistent(); } } /** * Add an atomic formula to the constraint. * Note: Constraint is modified in place. * @param term * @return * @throws XFailure */ public void addAtom(XTerm term) throws XFailure { if (!consistent) return; valid = false; if (roots == null) roots = CollectionFactory.<XNativeTerm,XPromise> newHashMap(); XNativeTerm t = (XNativeTerm) term; XPromise p = t.nfp(this); if (p != null) return; // nothing to do p = intern(t); } /** * Does this entail constraint other? * * @param t * @return */ public boolean entails(XConstraint other) { if (!consistent) return true; if (other == null || other.valid()) return true; EntailsVisitor ev = new EntailsVisitor(true, false, this); other.visit(ev); return ev.result(); } public void setInconsistent() { this.consistent = false; } public XConstraint leastUpperBound(XConstraint other) { if (! consistent) return other; if (! other.consistent()) return this; if (valid) return this; if (other.valid()) return other; XNativeConstraint result = new XNativeConstraint(); for (XTerm term : ((XNativeConstraint)other).constraints()) { try { if (entails(term)) result.addTerm(term); } catch (XFailure z) { result.setInconsistent(); } } return result; } public XConstraint residue(XConstraint other) { assert other.consistent(); XNativeConstraint result = new XNativeConstraint(); if (! consistent) return result; for (XTerm term : ((XNativeConstraint)other).constraints()) { try { if (! entails(term)) result.addTerm(term); } catch (XFailure z) { // since other is consistent, result must be. result.setInconsistent(); } } return result; } /** * Return a list of bindings t1-> t2 equivalent to the current * constraint. Equivalent to constraints(new ArrayList()). * * @return */ public List<XTerm> constraints() { if (roots == null) return new ArrayList<XTerm>(0); ConstraintGenerator cg = new ConstraintGenerator(true, false); visit(cg); return cg.result(); } public void visit( XGraphVisitor xg) { if (roots == null) return; Collection<XPromise> values = roots.values(); for (XPromise p : values) { boolean result = p.visit(null, xg, this); if (! result ) return; } } /** * Return a list of bindings t1-> t2 equivalent to * the current constraint except that equalities involving EQV variables * are ignored. * * @return */ public List<XTerm> extConstraints() { ConstraintGenerator cg = new ConstraintGenerator(true, false); visit(cg); return cg.result(); } /** * Return a list of bindings t1-> t2 equivalent to the current * constraint except that equalities involving only EQV variables are * ignored if dumpEQV is false, and equalities involving only fake fields * are ignored if hideFake is true. * * @return */ public List<XTerm> extConstraintsHideFake() { ConstraintGenerator cg = new ConstraintGenerator(true, true); visit(cg); return cg.result(); } /** * Does this entail a != b? * @param a * @param b * @return this |- a != b */ public boolean disEntails(XTerm term1, XTerm term2) { XNativeTerm t1 = (XNativeTerm)term1; XNativeTerm t2 = (XNativeTerm)term2; if (! consistent) return true; XPromise p1 = t1.nfp(this); if (p1 == null) return false; // this constraint knows nothing about t1. XPromise p2 = t2.nfp(this); if (p2 == null) return false; if (p1.isDisBoundTo(p2)) return true; Map<Object, XPromise> p1f = p1.fields(); if (p1f !=null) { Map<Object, XPromise> p2f = p2.fields(); if (p2f != null) for (Map.Entry<Object, XPromise> me : p1f.entrySet()) { Object field = me.getKey(); XPromise r1 = me.getValue().lookup(); XPromise r2 = p2f.get(field).lookup(); if (r1.isDisBoundTo(r2)) return true; } } return false; } /** * Does this entail a==b? * @param a * @param b * @return true iff this |- a==b */ public boolean entails(XTerm term1, XTerm term2) { XNativeTerm t1 = (XNativeTerm)term1; XNativeTerm t2 = (XNativeTerm)term2; if (!consistent) return true; XPromise p1 = t1.nfp(this); XPromise p2 = t2.nfp(this); return p1 == p2 || p1.term().equals(p2.term()); } /** * Does this entail c, and c entail this? * * @param other -- null is treated as a valid constraint * @return */ public boolean equiv(XNativeConstraint other) throws XFailure { boolean result = entails(other); if (result) result = other == null ? valid : other.entails(this); return result; } /** Return true if this constraint entails t. */ public boolean entails(XTerm t) { if (t instanceof XEquals) { XEquals f = (XEquals) t; XNativeTerm left = f.left(); XNativeTerm right = f.right(); if (entails(left, right)) return true; if (right instanceof XEquals) { XEquals r = (XEquals) right; if (entails(r.left(), r.right())) return entails(left, XNativeLit.TRUE); if (disEntails(r.left(), r.right())) { return entails(left, XNativeLit.FALSE); } } if (right instanceof XDisEquals) { XDisEquals r = (XDisEquals) right; if (entails(r.left(), r.right())) return entails(left, XNativeLit.FALSE); if (disEntails(r.left(), r.right())) return entails(left, XNativeLit.TRUE); } } else if (t instanceof XDisEquals) { XDisEquals f = (XDisEquals) t; XNativeTerm left = f.left(); XNativeTerm right = f.right(); if (disEntails(left, right)) return true; } else if (t instanceof XNativeFormula) { XNativeFormula<?> f = (XNativeFormula<?>) t; Object op = f.operator(); XNativeTerm[] args = f.arguments(); int n = args.length; for (XFormula<?> x : atoms()) { if (x.operator().equals(op)) { XTerm[] xargs = x.arguments(); if (n!= xargs.length) continue; int i=0; while(i < n && entails(args[i], xargs[i])) i++; if (i==n) return true; } } return false; } return false; } public String toString() { XNativeConstraint c = this; if (! c.consistent) return "{inconsistent}"; String str ="{"; final boolean exists_toString = false; if (exists_toString) { List<XNativeVar> eqvs = eqvs(); if (!eqvs.isEmpty()) { String temp = eqvs.toString(); str = "exists " + temp.substring(1, temp.length() - 1) + "."; } String constr = c.constraints().toString(); str += constr.substring(1, constr.length() - 1); } else { List<XTerm> l = c.extConstraintsHideFake(); for (XTerm t : l) { str += t.toString() + ", " ; } str = str.length() >= 2? str.substring(0, str.length() - 2) : str; } return str + "}"; } /** * Perform substitute y for x for every binding x -> y in bindings. * */ public XNativeConstraint substitute(Map<XNativeVar, XNativeTerm> subs) throws XFailure { XNativeConstraint c = this; for (Map.Entry<XNativeVar,XNativeTerm> e : subs.entrySet()) { XNativeVar x = e.getKey(); XNativeTerm y = e.getValue(); c = c.substitute(y, x); } return c; } /** * If y equals x, or x does not occur in this, return this, else copy * the constraint and return it after performing applySubstitution(y,x). * * */ public XNativeConstraint substitute(XNativeTerm y, XNativeVar x) throws XFailure { return substitute(new XNativeTerm[] { y }, new XNativeVar[] { x }); } public XNativeConstraint substitute(XNativeTerm[] ys, XNativeVar[] xs, boolean propagate) throws XFailure { return substitute(ys, xs); } /** * xs and ys must be of the same length. Perform substitute(ys[i], * xs[i]) for each i < xs.length. */ public XNativeConstraint substitute(XNativeTerm[] ys, XNativeVar[] xs) throws XFailure { assert (ys != null && xs != null); assert xs.length == ys.length; boolean eq = true; for (int i = 0; i < ys.length; i++) { XNativeTerm y = ys[i]; XNativeVar x = xs[i]; if (! y.equals(x)) eq = false; } if (eq) return this; if (! consistent) return this; // Don't do the quick occurrence check; x might occur in a self constraint. // XPromise last = lookupPartialOk(x); // if (last == null) return this; // x does not occur in this XNativeConstraint result = new XNativeConstraint(); for (XTerm term : constraints()) { XTerm t = term; // if term is y==x.f, the subst will produce y==y.f, which is a cycle--bad! // if (term instanceof XEquals_c) { // XEquals_c eq = (XEquals_c) term; // XNativeTerm l = eq.left(); // XNativeTerm r = eq.right(); // if (y.equals(l) || y.equals(r)) // continue; // } for (int i = 0; i < ys.length; i++) { XTerm y = ys[i]; XNativeVar x = xs[i]; t = t.subst(y, x); } // t = t.subst(result.self(), self(), true); try { result.addTerm(t); } catch (XFailure z) { throw z;} } // XNativeConstraint_c result = clone(); // result.valid = true; // result.applySubstitution(y,x); return result; } /** * Does this constraint contain occurrences of the variable v? * * @param v * @return true iff v is a root variable of this. */ public boolean hasVar(XVar v) { return roots != null && roots.keySet().contains(v);} /** * Add the binding term=true to the constraint. * * @param term -- must be of type Boolean. * @return new constraint with term=true added. * @throws SemanticException */ // FIXME: need to convert f(g(x)) into \exists y. f(y) && g(x) = y when f and g both atoms // This is needed for Nelson-Oppen to work correctly. // Each atom should be a root. public void addTerm(XTerm term) throws XFailure { if (term.isAtomicFormula()) addAtom(term); else if (term instanceof XNativeVar) addBinding(term, XNativeLit.TRUE); /*else if (term instanceof XNot) { XNot t = (XNot) term; if (t.unaryArg() instanceof XVar) addBinding(t.unaryArg(), XNativeTerms.FALSE); if (t.unaryArg() instanceof XNot) addTerm(((XNot) t.unaryArg()).unaryArg()); }*/ else if (term instanceof XAnd) { XAnd t = (XAnd) term; addTerm(t.left()); addTerm(t.right()); } else if (term instanceof XEquals) { XEquals eq = (XEquals) term; XNativeTerm left = eq.left(); XNativeTerm right = eq.right(); addBinding(left, right); } else if (term instanceof XDisEquals) { XDisEquals dq = (XDisEquals) term; XNativeTerm left = dq.left(); XNativeTerm right = dq.right(); addDisBinding(left, right); } else { throw new XFailure("Unexpected term |" + term + "|"); } } // *****************************************************************INTERNAL ROUTINES /** * Return the promise obtained by interning this term in the constraint. * This may result in new promises being added to the graph maintained * by the constraint. * <p>term: Literal -- return the literal. * <p> term:LocalVariable, Special, Here Check if term is already in the roots * maintained by the constraint. If so, return the root, if not add a * promise to the roots and return it. * <p> term: XField. Start with the rootVar x and follow the path f1...fk, * if term=x.f1...fk. If the graph contains no nodes after fi, * for some i < k, add promises into the graph from fi+1...fk. * Return the last promise. * * <p> Package protected -- should only be used in the implementation of the constraint * system. * @param term * @return * @throws XFailure */ XPromise intern(XNativeTerm term) { return intern(term, null);} /** * Used to implement substitution: if last != null, term, is substituted for * the term that was interned previously to produce the promise last. This is accomplished by * returning last as the promise obtained by interning term, unless term is a literal, in which * case last is forwarded to term, and term is returned. This way incoming and outgoing edges * (from fields) from last are preserved, but term now "becomes" last. * Required: on entry, last.value == null. * The code will work even if we have literals that are at types where properties are permitted. * @param term * @param last * @return */ XPromise intern(XNativeTerm term, XPromise last) { assert term != null; return term.internIntoConstraint(this, last); } XPromise internBaseVar(XNativeVar baseVar, boolean replaceP, XPromise last) { if (roots == null) roots = CollectionFactory.<XNativeTerm,XPromise> newHashMap(); XPromise p = roots.get(baseVar); if (p == null) { p = (replaceP && last != null) ? last : new XPromise(baseVar); roots.put(baseVar, p); } return p; } void addPromise(XNativeTerm p, XPromise node) { if (roots == null) roots = CollectionFactory.<XNativeTerm,XPromise> newHashMap(); roots.put(p, node); } public Set<XNativeTerm> getTerms() { if (roots == null) return new HashSet<XNativeTerm>(); return roots.keySet(); } }