/****************************************************************************** * Copyright (c) 2009 - 2015 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *****************************************************************************/ package com.ibm.wala.memsat.util; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import com.ibm.wala.util.debug.Assertions; import kodkod.ast.BinaryExpression; import kodkod.ast.BinaryFormula; import kodkod.ast.BinaryIntExpression; import kodkod.ast.ComparisonFormula; import kodkod.ast.ConstantFormula; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.IfExpression; import kodkod.ast.IfIntExpression; import kodkod.ast.IntComparisonFormula; import kodkod.ast.IntConstant; import kodkod.ast.IntExpression; import kodkod.ast.NaryExpression; import kodkod.ast.NaryFormula; import kodkod.ast.NaryIntExpression; import kodkod.ast.Node; import kodkod.ast.NotFormula; import kodkod.ast.UnaryExpression; import kodkod.ast.UnaryIntExpression; import kodkod.ast.operator.ExprCompOperator; import kodkod.ast.operator.ExprOperator; import kodkod.ast.operator.FormulaOperator; import kodkod.ast.operator.IntCompOperator; import kodkod.ast.operator.IntOperator; import kodkod.ast.visitor.AbstractReplacer; import kodkod.util.ints.SparseSequence; import kodkod.util.ints.TreeSequence; /** * Provides helper methods for performing common simplifications on Kodkod nodes. * @author etorlak */ abstract class Simplifier extends AbstractReplacer { final SparseSequence<Expression> empties; final SparseSequence<IntConstant> constants; /** * Constructs a new simplifier that will use the given collection * of empty expressions and integer constants. The empties sequence must map each of its indices * to an empty expression of the same arity as the index. The constants sequence must map each of * its indices to a Kodkod constant with the same value as the index. */ @SuppressWarnings("unchecked") Simplifier(SparseSequence<Expression> empties, SparseSequence<IntConstant> constants) { super(Collections.EMPTY_SET); this.empties = empties; this.constants = constants; } /** * Constructs a new simplifier with the empties sequence initializes with * Expression.NONE and Nodes.NONE2. */ Simplifier() { this(new TreeSequence<Expression>(), new TreeSequence<IntConstant>()); empties.put(1, Expression.NONE); empties.put(2, Nodes.NONE2); } /** * Caches the given replacement for the specified node, if this is * a caching visitor. Otherwise does nothing. The method returns * the replacement node. * @effects this.cache' = this.cache ++ node->replacement, * @return replacement */ protected <N extends Node> N cache(N node, N replacement) { assert replacement != null; cache.put(node, replacement); return replacement; } /** @return true if expr is equal to a canonical empty expression */ final boolean isEmpty(Expression expr) { return empties.get(expr.arity())==expr; } /** @return canonical empty expression of the given arity */ final Expression empty(int arity) { Expression empty = empties.get(arity); if (empty==null) { empty = Nodes.empty(arity); empties.put(arity, empty); } return empty; } /** @return canonical constant expression for the given int expression, if it is a constant, or null otherwise */ final IntConstant constant(IntExpression expr) { if (expr.getClass()==IntConstant.class) { final IntConstant constant = (IntConstant) expr; final int val = constant.value(); final IntConstant ret = constants.get(val); if (ret!=null) return ret; constants.put(val, constant); return constant; } return null; } /** @return canonical integer constant with the given value. */ final IntConstant constant(int val) { IntConstant ret = constants.get(val); if (ret==null) { ret = IntConstant.constant(val); constants.put(val, ret); } return ret; } /** @return visits all nodes returned by the given iterator and returns the result in a list. */ @SuppressWarnings("unchecked") final <N extends Node> List<N> visitAll(Iterable<N> iterable) { final List<N> result = new ArrayList<N>(); for(Iterator<N> itr = iterable.iterator(); itr.hasNext(); ) { result.add((N)itr.next().accept(this)); } return result; } /** @return true if the two iterators return identical elements; otherwise returns false */ final boolean allSame(Iterable<?> first, Iterable<?> second) { final Iterator<?> itr1 = first.iterator(), itr2 = second.iterator(); while(itr1.hasNext() && itr2.hasNext()) if (itr1.next()!=itr2.next()) return false; return !itr1.hasNext() && !itr2.hasNext(); } /** @return a simplification of left op right, if possible, or null otherwise. */ final Expression simplify(ExprOperator op, Expression left, Expression right) { switch(op) { case UNION : case OVERRIDE : if (left==right) { return left; } else if (isEmpty(left)) { return right; } else if (isEmpty(right)) { return left; } break; case JOIN : if (isEmpty(left) || isEmpty(right)) { return empty(left.arity()+right.arity()-2); } break; case PRODUCT : if (isEmpty(left) || isEmpty(right)) { return empty(left.arity()+right.arity()); } break; case DIFFERENCE : if (left==right) { return empty(left.arity()); } else if (isEmpty(left)||isEmpty(right)) { return left; } break; case INTERSECTION : if (left==right) { return left; } else if (isEmpty(left)) { return left; } else if (isEmpty(right)) { return right; } break; default : Assertions.UNREACHABLE(); } return null; } /** * @requires op.nary() * @return simplification of the given operand list **/ final List<Expression> simplify(ExprOperator op, List<Expression> children) { switch(op) { case UNION : case OVERRIDE : for(Iterator<Expression> itr = children.iterator(); itr.hasNext(); ) { final Expression child = itr.next(); if (isEmpty(child)) itr.remove(); } break; case PRODUCT : for(Expression child : children) { if (isEmpty(child)) return Collections.singletonList(empty(children.size())); } break; case INTERSECTION : for(Expression child : children) { if (isEmpty(child)) return Collections.singletonList(child); } break; default : Assertions.UNREACHABLE(); } return children; } boolean isTrue(Formula formula) { return formula==Formula.TRUE; } boolean isFalse(Formula formula) { return formula==Formula.FALSE; } /** @return a simplification of !child, if possible, or null otherwise. */ final Formula simplify(Formula child) { // can only simplify in the case of not with constants for the Propagator to work correctly if (child==Formula.TRUE) return Formula.FALSE; else if (child==Formula.FALSE) return Formula.TRUE; else if (child.getClass()==NotFormula.class) return ((NotFormula)child).formula(); else return null; } /** @return true if left is a NotFormula with right as its child or vice versa */ final boolean areInverses(Formula left, Formula right) { return ((left.getClass() == NotFormula.class) && ((NotFormula)left).formula()==right) || ((right.getClass() == NotFormula.class) && ((NotFormula)right).formula()==left); } /** @return a simplification of left op right, if possible, or null otherwise. */ final Formula simplify(FormulaOperator op, Formula left, Formula right) { switch(op) { case AND : if (left==right) { return left; } else if (isTrue(left)) { return right; } else if (isTrue(right)) { return left; } else if (isFalse(left) || isFalse(right) || areInverses(left, right)) { return Formula.FALSE; } break; case OR : if (left==right) { return left; } else if (isFalse(left)) { return right; } else if (isFalse(right)) { return left; } else if (isTrue(left) || isTrue(right) || areInverses(left, right)) { return Formula.TRUE; } break; case IMPLIES : // !left or right if (left==right) { return Formula.TRUE; } else if (isTrue(left)) { return right; } else if (isFalse(right)) { return left; } else if (isFalse(left) || isTrue(right)) { return Formula.TRUE; } break; case IFF : // (left and right) or (!left and !right) if (left==right) { return Formula.TRUE; } else if (isTrue(left)) { return right; } else if (isFalse(left)) { return right.not().accept(this); } else if (isTrue(right)) { return left; } else if (isFalse(right)) { return left.not().accept(this); } else if (areInverses(left, right)) { return Formula.FALSE; } break; default : Assertions.UNREACHABLE(); } return null; } /** * @requires op.nary * @return simplification of the given operand set **/ final List<Formula> simplify(FormulaOperator op, List<Formula> children) { final boolean sc = op!=FormulaOperator.AND; final Formula scFormula = ConstantFormula.constant(sc); final Set<Formula> pos = new LinkedHashSet<Formula>(children); final Set<Formula> negs = new LinkedHashSet<Formula>(); children.retainAll(pos); if (sc) { for(Iterator<Formula> itr = children.iterator(); itr.hasNext();) { final Formula child = itr.next(); if (isTrue(child)) return Collections.singletonList(scFormula); else if (isFalse(child)) itr.remove(); } } else { for(Iterator<Formula> itr = children.iterator(); itr.hasNext();) { final Formula child = itr.next(); if (isFalse(child)) return Collections.singletonList(scFormula); else if (isTrue(child)) itr.remove(); } } for(Iterator<Formula> itr = children.iterator(); itr.hasNext();) { final Formula child = itr.next(); if (negs.contains(child)) { return Collections.singletonList(scFormula); } else if (child.getClass() == NotFormula.class) { final NotFormula notChild = (NotFormula)child; if (pos.contains(notChild.formula())) return Collections.singletonList(scFormula); negs.add(notChild.formula()); } } return children; } /** @return a simplification of op child, if possible, or null otherwise. */ final IntExpression simplify(IntOperator op, IntExpression child) { if (child==constant(0)) { switch(op) { case ABS : case SGN : case NEG : return child; case NOT : return constant(-1); default : Assertions.UNREACHABLE(); } } return null; } /** @return a simplification of left op right, if possible, or null otherwise. */ final IntExpression simplify(IntOperator op, IntExpression left, IntExpression right) { final boolean lzero = left==constant(0), rzero = right==constant(0); switch(op) { case PLUS : case OR : case XOR : if (lzero) { return right; } else if (rzero) { return left; } break; case MULTIPLY : case AND : if (lzero) { return left; } else if (rzero) { return right; } break; case MINUS : if (lzero) { return right.negate().accept(this); } else if (rzero) { return left; } break; case SHA : case SHL : case SHR : if (lzero || rzero) { return left; } break; case DIVIDE : case MODULO : if (lzero) { return left; } break; default : Assertions.UNREACHABLE(); } return null; } /** * @requires op.nary * @return simplification of the given operand list **/ final List<IntExpression> simplify(IntOperator op, List<IntExpression> children) { final IntExpression zero = constant(0); switch(op) { case PLUS : case OR : for(Iterator<IntExpression> itr = children.iterator(); itr.hasNext(); ) { if (itr.next()==zero) itr.remove(); } break; case MULTIPLY : case AND : for(IntExpression child : children) { if (child==zero) return Collections.singletonList(zero); } break; default : Assertions.UNREACHABLE(); } return children; } /** * @return simplification of the given ITE application or null if none is possible */ final <T> T simplify(Formula cond, T thenExpr, T elseExpr) { if (isTrue(cond)) return thenExpr; else if (isFalse(cond)) return elseExpr; else if (thenExpr==elseExpr) return thenExpr; else return null; } public Expression visit(IfExpression expr) { Expression ret = lookup(expr); if (ret!=null) return ret; final Formula cond = expr.condition().accept(this); final Expression thenExpr = expr.thenExpr().accept(this); final Expression elseExpr = expr.elseExpr().accept(this); ret = simplify(cond,thenExpr,elseExpr); if (ret==null) { ret = cond==expr.condition()&&thenExpr==expr.thenExpr()&&elseExpr==expr.elseExpr() ? expr : cond.thenElse(thenExpr, elseExpr); } return cache(expr,ret); } public IntExpression visit(IfIntExpression expr) { IntExpression ret = lookup(expr); if (ret!=null) return ret; final Formula cond = expr.condition().accept(this); final IntExpression thenExpr = expr.thenExpr().accept(this); final IntExpression elseExpr = expr.elseExpr().accept(this); ret = simplify(cond,thenExpr,elseExpr); if (ret==null) { ret = cond==expr.condition()&&thenExpr==expr.thenExpr()&&elseExpr==expr.elseExpr() ? expr : cond.thenElse(thenExpr, elseExpr); } return cache(expr,ret); } public Formula visit(NotFormula not) { Formula ret = lookup(not); if (ret!=null) return ret; final Formula child = not.formula().accept(this); ret = simplify(child); if (ret==null) { ret = child==not.formula() ? not : child.not(); } return cache(not,ret); } public Formula visit(ComparisonFormula formula) { Formula ret = lookup(formula); if (ret!=null) return ret; final ExprCompOperator op = formula.op(); final Expression left = formula.left().accept(this); final Expression right = formula.right().accept(this); if (left==right) return cache(formula,Formula.TRUE); ret = left==formula.left()&&right==formula.right() ? formula : left.compare(op, right); return cache(formula,ret); } public Formula visit(IntComparisonFormula formula) { Formula ret = lookup(formula); if (ret!=null) return ret; final IntCompOperator op = formula.op(); final IntExpression left = formula.left().accept(this); final IntExpression right = formula.right().accept(this); if (left==right) return cache(formula,Formula.TRUE); ret = left==formula.left()&&right==formula.right() ? formula : left.compare(op, right); return cache(formula,ret); } public Formula visit(BinaryFormula expr) { Formula ret = lookup(expr); if (ret!=null) return ret; final FormulaOperator op = expr.op(); final Formula left = expr.left().accept(this); final Formula right = expr.right().accept(this); ret = simplify(op, left, right); if (ret==null) { ret = left==expr.left()&&right==expr.right() ? expr : left.compose(op, right); } return cache(expr,ret); } public Formula visit(NaryFormula formula) { Formula ret = lookup(formula); if (ret!=null) return ret; final FormulaOperator op = formula.op(); final List<Formula> children = simplify(op, visitAll(formula)); final int size = children.size(); if (size<2) { return cache(formula, Formula.compose(op, children)); } else { ret = formula.size()==size && allSame(formula,children) ? formula : Formula.compose(op, children); return cache(formula,ret); } } public Expression visit(BinaryExpression expr) { Expression ret = lookup(expr); if (ret!=null) return ret; final ExprOperator op = expr.op(); final Expression left = expr.left().accept(this); final Expression right = expr.right().accept(this); ret = simplify(op, left, right); if (ret==null) { ret = left==expr.left()&&right==expr.right() ? expr : left.compose(op, right); } return cache(expr,ret); } public Expression visit(NaryExpression expr) { Expression ret = lookup(expr); if (ret!=null) return ret; final ExprOperator op = expr.op(); final List<Expression> children = simplify(op, visitAll(expr)); final int size = children.size(); switch(size) { case 0 : return cache(expr, empty(expr.arity())); case 1 : return cache(expr, children.get(0)); default : ret = expr.size()==size && allSame(expr,children) ? expr : Expression.compose(op, children); return cache(expr,ret); } } public Expression visit(UnaryExpression expr) { Expression ret = lookup(expr); if (ret!=null) return ret; final ExprOperator op = expr.op(); final Expression child = expr.expression().accept(this); if (isEmpty(child)) return cache(expr, child); ret = child==expr.expression() ? expr : child.apply(op); return cache(expr,ret); } public IntExpression visit(UnaryIntExpression expr) { IntExpression ret = lookup(expr); if (ret!=null) return ret; final IntOperator op = expr.op(); final IntExpression child = expr.intExpr().accept(this); ret = simplify(op, child); if (ret==null) { ret = child==expr.intExpr() ? expr : child.apply(op); } return cache(expr,ret); } public IntExpression visit(BinaryIntExpression expr) { IntExpression ret = lookup(expr); if (ret!=null) return ret; final IntOperator op = expr.op(); final IntExpression left = expr.left().accept(this); final IntExpression right = expr.right().accept(this); ret = simplify(op, left, right); if (ret==null) { ret = left==expr.left()&&right==expr.right() ? expr : left.compose(op, right); } return cache(expr,ret); } public IntExpression visit(NaryIntExpression expr) { IntExpression ret = lookup(expr); if (ret!=null) return ret; final IntOperator op = expr.op(); final List<IntExpression> children = simplify(op, visitAll(expr)); final int size = children.size(); switch(size) { case 0 : return cache(expr, constant(0)); case 1 : return cache(expr, children.get(0)); default : ret = expr.size()==size && allSame(expr,children) ? expr : IntExpression.compose(op, children); return cache(expr,ret); } } }