/* * Kodkod -- Copyright (c) 2005-present, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.engine.fol2sat; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import kodkod.ast.Node; import kodkod.ast.Variable; import kodkod.engine.bool.BooleanConstant; import kodkod.engine.bool.BooleanMatrix; import kodkod.util.nodes.AnnotatedNode; /** * Manages the caching policy for a translation from FOL to boolean. * In particular it determines which translations * to cache, when to throw them out of the cache, etc. * * @specfield node: Node // node being translated * @specfield cached: node.*children // the nodes whose translations are cached * @specfield cache: cached -> (Object ->lone Environment) * @author Emina Torlak */ final class FOL2BoolCache { private final Map<Node,Record> cache; /** * Constructs a new translation cache for the given annotated node. * @ensures this.node' = annotated.node */ FOL2BoolCache(AnnotatedNode<? extends Node> annotated) { final CacheCollector collector = new CacheCollector(annotated.sharedNodes()); annotated.node().accept(collector); this.cache = new IdentityHashMap<Node, Record>(collector.cache().size()); for(Map.Entry<Node, Set<Variable>> e : collector.cache().entrySet()) { Set<Variable> freeVars = e.getValue(); if (freeVars.isEmpty()) this.cache.put(e.getKey(), new NoVarRecord()); else this.cache.put(e.getKey(), new MultiVarRecord(freeVars)); } } /** * If the translation of the given node, with its free variables * bound as they are in the given environment, has been cached, * the cached value is returned. Otherwise, null is returned. * @return this.cache[node][Object] in env.map => * this.cache[node].map, null */ @SuppressWarnings("unchecked") <T> T lookup(Node node, Environment<BooleanMatrix> env) { final Record info = cache.get(node); return info==null ? null : (T) info.get(env); } /** * Caches the given translation for the specified node, if the given node is * in this.cached. Otherwise does nothing. * The method returns the specified translation. * @ensures node in this.cached => * this.cache' = this.cache ++ node->translation->env, * this.cache' = this.cache * @return translation */ final <T> T cache(Node node, T translation, Environment<BooleanMatrix> env) { final Record info = cache.get(node); if (info != null) { info.set(translation, env); } return translation; } /** * Collects the free variables of the nodes in a given AST whose * translations should be cached. * @specfield root: Node * @specfield cached: root.*children -> Set<Variable> * @invariant all c: root.*children | some cached[c] => cached[c] = freeVariables(c) * @author Emina Torlak */ private static final class CacheCollector extends FreeVariableCollector { /** * Constructs a new cache collector. */ protected CacheCollector(Set<Node> cached) { super(cached); } /** * Returns this.cache. This method should be called *after* the * visitor has been applied to this.root. * @return this.cache */ final Map<Node,Set<Variable>> cache() { return cache; } /** * We record the set of free variables for the given node if the node is shared, * or if it has free variables, none of which is the most recently declared variable. * @ensures node in sharedNodes || * ((node.^(~children) in (QuantifiedFormula + Comprehension)) && * (some varsInScope.top() => !freeVars.contains(varsInScope.top()))) => * this.cache' = this.cache ++ node->varsInScope, * this.cache' = this.cache * @return freeVars */ @Override protected final Set<Variable> cache(Node node, Set<Variable> freeVars) { if (cached.contains(node) || !varsInScope.empty() && !freeVars.contains(varsInScope.peek())) { cache.put(node, reduce(freeVars)); } return freeVars; } } /** * A container class that stores the translation of a shared node * (BooleanValue for formulas and BooleanMatrix for expressions) * and bindings for the node's free variables which were used to * generate the translation. * Storing the bindings is necessary for proper handling of * sharing within quantified formulas and comprehensions. * This implementation assumes that each free variable is * mapped to a BooleanMatrix of density one, whose sole entry * is the BooleanConstant TRUE. * @specfield varBinding: Variable -> lone int * @specfield translation: lone Object */ private static abstract class Record { Object translation; /** * Returns this.translation if the given environment * has the same mappings for the free variables of * the translated node as the ones used to generate * this.translation. Otherwise returns null. * @requires all v: varBinding.int | some e.lookup(v) * @return all v: varBinding.int | e.lookup(v).get(varBinding[v])=TRUE => this.translation, null * @throws NullPointerException e = null */ abstract Object get(Environment<BooleanMatrix> e); /** * Sets this.translation to the given translation * and sets the free variable bindings to those * given by the specified environment. * @requires all v: varBinding.int | some env.lookup(v) * @ensures this.translation' = translation && * this.varBinding' = * {v: this.varBinding.int, tupleIndex: int | * tupleIndex = env.lookup(v).iterator().next().index() } */ abstract void set(Object transl, Environment<BooleanMatrix> env); } /** * A TranslationInfo for a node with one or more free variables. */ private static final class MultiVarRecord extends Record { final Variable[] vars; final int[] tuples; /** * Constructs a translation unit for a node which * has the given set of free variables. * @ensures this.freeVariables' = vars && * no this.translation' */ MultiVarRecord(Set<Variable> freeVariables) { this.vars = freeVariables.toArray(new Variable[freeVariables.size()]); this.tuples = new int[freeVariables.size()]; } /** * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#get(kodkod.engine.fol2sat.Environment) */ Object get(Environment<BooleanMatrix> e) { if (translation==null) return null; for(int i = 0; i < vars.length; i++) { if (e.lookup(vars[i]).get(tuples[i])!=BooleanConstant.TRUE) return null; } return translation; } /** * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#set(java.lang.Object, kodkod.engine.fol2sat.Environment) */ void set(Object transl, Environment<BooleanMatrix> env) { translation = transl; for(int i = 0; i < vars.length; i++) { final BooleanMatrix varVal = env.lookup(vars[i]); tuples[i] = varVal.iterator().next().index(); if (transl==varVal) { translation = varVal.clone(); } } } /** * @see java.lang.Object#toString() */ public String toString() { final StringBuilder b = new StringBuilder("{"); b.append(String.valueOf(translation)); for(int i = 0; i < vars.length; i++) { b.append(" ("); b.append(vars[i]); b.append(", "); b.append(tuples[i]); b.append(")"); } b.append("}"); return b.toString(); } } /** * A TranslationInfo for a node with no free variables. */ private static final class NoVarRecord extends Record { /** * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#get(kodkod.engine.fol2sat.Environment) */ Object get(Environment<BooleanMatrix> e) { return translation; } /** * @see kodkod.engine.fol2sat.FOL2BoolCache.Record#set(java.lang.Object, kodkod.engine.fol2sat.Environment) */ void set(Object transl, Environment<BooleanMatrix> env) { translation = transl; } /** * @see java.lang.Object#toString() */ public String toString() { return "{" + translation+ "}"; } } }