/******************************************************************************
* 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.representation;
import static com.ibm.wala.memsat.frontEnd.IRType.BOOLEAN;
import static com.ibm.wala.memsat.frontEnd.IRType.INTEGER;
import static com.ibm.wala.memsat.frontEnd.IRType.OBJECT;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.memsat.Options;
import com.ibm.wala.memsat.frontEnd.IRType;
import com.ibm.wala.memsat.frontEnd.WalaInformation;
import com.ibm.wala.memsat.util.Strings;
import com.ibm.wala.types.TypeReference;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.IntConstant;
import kodkod.ast.IntExpression;
import kodkod.ast.Relation;
import kodkod.engine.Evaluator;
import kodkod.instance.Bounds;
import kodkod.instance.TupleFactory;
import kodkod.instance.TupleSet;
import kodkod.util.collections.Containers;
/**
* A factory for generating Kodkod Expressions that
* represent Wala primitives and instances. These
* values are constant in the sense that their relational values
* are known a priori and do not have to be solved for. (In
* other words, the relations that make up the representations of
* Wala primitives and instances are Kodkod constants.)
*
* @specfield info: WalaInformation
* @specfield options: Options // options used for determining bitwidth for int representation, etc.
* @specfield primitives: (boolean ->one Formula) +
* (int ->one IntExpression) + (float ->one RealExpression) + (null ->one Expression) +
* (String ->one Expression)
* @specfield interpreters: INTEGER ->one Interpreter<IntExpression> +
* REAL ->one Interpreter<RealExpression> + BOOLEAN ->one Interpreter<Formula> +
* OBJECT ->one Interpreter<Expression>
* @specfield instances: info.relevantClasses() + String + null ->one Expression
* @author Emina Torlak
*/
public final class ConstantFactory {
static final Relation TRUE = Relation.unary("true");
static final Relation FALSE = Relation.unary("false");
static final Relation NULL = Relation.unary("null");
private final Interpreter<Formula> bools;
private final Interpreter<Expression> objects;
private final Interpreter<IntExpression> ints;
private final Interpreter<RealExpression> reals;
private final int bitwidth;
private final Map<InstanceKey, Relation> sets;
private final Map<InstanceKey, Relation[]> instances;
private final Set<InstanceKey> openWorldKeys = new HashSet<InstanceKey>();
private final int openWorldScopeSize;
/**
* Constructs a primitive factory for the given options.
* @effects this.options' = options
*/
ConstantFactory(WalaInformation info, Options options) {
this.bools = bools();
this.objects = objects();
this.bitwidth = options.kodkodOptions().bitwidth();
this.ints = ints();
this.reals = RealExpression.interpreter(ints);
this.sets = new LinkedHashMap<InstanceKey, Relation>();
this.instances = new LinkedHashMap<InstanceKey, Relation[]>();
for(Map.Entry<InstanceKey, String> entry : Strings.instanceNames(info.relevantClasses()).entrySet()) {
InstanceKey key = entry.getKey();
String name = entry.getValue();
sets.put(key, Relation.unary(name));
Relation[] rels = new Relation[info.cardinality(key)];
for(int i = 0; i < rels.length; i++) {
rels[i] = Relation.unary(name+i);
}
instances.put(key, rels);
if (info.openWorldType(key)) {
openWorldKeys.add(key);
}
}
openWorldScopeSize = options.openWorldScopeSize();
}
/**
* Returns an ordered set of atom objects needed to represent all primitives
* generated by this factory. In particular, the first two atoms represent
* the values "true" and "false",
* the next <tt>this.options.bitsForIntegers()</tt> atoms are Integer objects
* representing the powers two (in the increasing
* order of absolute values) needed to represent all <tt>options.bitsForIntegers()</tt>-bit integers
* in two's complement; the next atom represents the null value, and the remaining
* atoms uniquely represent the instances of this.info.relevantClasses().
* @return an ordered set of atom objects needed to represent all primitives
* generated by this factory
*/
Set<?> atoms() {
final Set<Object> atoms = new LinkedHashSet<Object>();
atoms.add(TRUE);
atoms.add(FALSE);
final int msb = bitwidth-1;
for(int i = 0, pos = msb; i < pos; i++) {
atoms.add(Integer.valueOf(1<<i));
}
atoms.add(Integer.valueOf(-(1<<msb)));
atoms.add(NULL);
for(Map.Entry<InstanceKey,Relation[]> entry : instances.entrySet()) {
for(Relation rel : entry.getValue()) {
atoms.add(rel);
}
}
return atoms;
}
/**
* Returns a tupleset containing all constants of the given type.
* In particular, for type=OBJECT, singleton set containing the null
* atom is returned; for type=INTEGER, integer atoms are returned;
* for type=BOOLEAN, true and false atoms are returned; and for type=REAL,
* all real number representable using available integers are returned.
* @requires this.atoms() in factory.universe.atoms[int]
* @return tupleset containing all constants atoms of the given type.
*/
public TupleSet constantAtoms(TupleFactory factory, IRType type) {
switch(type) {
case BOOLEAN : return factory.setOf(TRUE, FALSE);
case OBJECT : return factory.setOf(NULL);
case INTEGER : case REAL :
final TupleSet ints = factory.noneOf(1);
final int msb = bitwidth-1;
for(int i = 0; i < msb; i++) {
ints.add(factory.tuple(Integer.valueOf(1<<i)));
}
ints.add(factory.tuple(Integer.valueOf(-(1<<msb))));
return type==INTEGER ? ints : RealExpression.allReals(ints);
default :
throw new AssertionError("unreachable code");
}
}
/**
* Returns the default atoms for the given type (FALSE for booleans,
* NULL for objects, and 0 for integers and reals).
* @requires this.atoms() in factory.universe.atoms[int]
* @return default atoms for the given type (FALSE for booleans,
* NULL for objects, and 0 for integers and reals).
*/
TupleSet defaultAtoms(TupleFactory factory, IRType type) {
switch(type) {
case BOOLEAN : return factory.setOf(FALSE);
case OBJECT : return factory.setOf(NULL);
case INTEGER : case REAL :
return factory.noneOf(1);
default :
throw new AssertionError("unreachable code");
}
}
/**
* Returns all relations that represent all instances.
* @return this.instances[univ]
*/
Collection<? extends Expression> setExpressions() { return sets.values(); }
/**
* Returns a tupleset containing all instances (atoms) of the given type partition.
* @requires this.atoms() in factory.universe.atoms[int]
* @return a tupleset containing all instances (atoms) of the given type partition.
*/
TupleSet instanceAtoms(TupleFactory factory, InstanceKey typeKey) {
final TupleSet s = factory.noneOf(1);
for(Relation instance : instances.get(typeKey)) {
s.add(factory.tuple(instance));
}
return s;
}
/**
* Returns a tupleset containing all instances (atoms) of the given type partition
* that are in the open world.
* @requires this.atoms() in factory.universe.atoms[int]
* @return a tupleset containing all instances (atoms) of the given type partition that are in the open world.
*/
TupleSet openInstanceAtoms(TupleFactory factory, InstanceKey typeKey) {
if (openWorldKeys.contains(typeKey)) {
final TupleSet s = factory.noneOf(1);
Relation[] atoms = instances.get(typeKey);
for(int i = 0; i < openWorldScopeSize; i++) {
s.add(factory.tuple( atoms[ atoms.length - i - 1 ] ));
}
return s;
} else {
return factory.noneOf(1);
}
}
/**
* Returns a tupleset containing all instances (atoms) of the given type partition
* that are in the closed world.
* @requires this.atoms() in factory.universe.atoms[int]
* @return a tupleset containing all instances (atoms) of the given type partition that are in the closed world.
*/
TupleSet closedInstanceAtoms(TupleFactory factory, InstanceKey typeKey) {
if (openWorldKeys.contains(typeKey)) {
final TupleSet s = factory.noneOf(1);
Relation[] atoms = instances.get(typeKey);
for(int i = 0; i < atoms.length - openWorldScopeSize; i++) {
s.add(factory.tuple( atoms[i] ));
}
return s;
} else {
return instanceAtoms(factory, typeKey);
}
}
/**
* Returns a tupleset containing all instances (atoms) of the given type partitions.
* @requires this.atoms() in factory.universe.atoms[int]
* @requires types in this.instances
* @return a tupleset containing all instances (atoms) of the given type partitions.
*/
public TupleSet instanceAtoms(TupleFactory factory, Set<InstanceKey> typeKeys) {
final TupleSet s = factory.noneOf(1);
for(InstanceKey set : typeKeys) {
if (instances.containsKey(set)) {
for(Relation instance : instances.get(set)) {
s.add(factory.tuple(instance));
}
}
}
return s;
}
/**
* Returns an expression that evaluates to all instances of the given type partition
* that are in the closed world.
* @return an expression that evaluates to all instances of the given type partition
* that are in the closed world.
*/
Expression closedValuesOf(InstanceKey typeKey) {
if (openWorldKeys.contains(typeKey)) {
final Relation[] values = instances.get(typeKey);
final int closed = values.length - openWorldScopeSize;
final Expression[] closedValues = new Expression[closed];
System.arraycopy(values, 0, closedValues, 0, closed);
return Expression.union(closedValues);
} else {
return valueOf(typeKey);
}
}
/**
* Returns an expression that evaluates to all instances of the given type partition
* that are in the open world.
* @return an expression that evaluates to all instances of the given type partition
* that are in the open world.
*/
Expression openValuesOf(InstanceKey typeKey) {
if (openWorldKeys.contains(typeKey)) {
final Relation[] values = instances.get(typeKey);
final Expression[] openValues = new Expression[openWorldScopeSize];
System.arraycopy(values, values.length-openWorldScopeSize, openValues, 0, openWorldScopeSize);
return Expression.union(openValues);
} else {
return Expression.NONE;
}
}
/**
* Adds bounds for all relations and integers generated by this factory
* to the given bounds.
* @requires atoms() in bounds.universe().atoms[int]
* @effects adds bounds for all relations and integers to the given bounds
*/
void boundAll(Bounds bounds) {
final TupleFactory f = bounds.universe().factory();
// bound primitives
bounds.boundExactly(TRUE, f.setOf(TRUE));
bounds.boundExactly(FALSE, f.setOf(FALSE));
bounds.boundExactly(NULL, f.setOf(NULL));
// bound ints
final int msb = bitwidth-1;
for(int i = 0; i < msb; i++) {
bounds.boundExactly(1<<i, f.setOf(Integer.valueOf(1<<i)));
}
bounds.boundExactly(-(1<<msb), f.setOf(Integer.valueOf(-(1<<msb))));
// bound instances and instantiables
for(InstanceKey key : sets.keySet()) {
Relation all = sets.get(key);
TupleSet val = f.setOf(NULL);
for(Relation rel : instances.get(key)) {
val.add(f.tuple(rel));
bounds.boundExactly(rel, f.setOf(rel));
}
bounds.boundExactly(all, val);
}
}
/**
* Returns an expression that models <tt>null</tt>.
* @return this.primitives[null]
*/
public final Expression nil() { return NULL; }
/**
* Returns an iterator over the expressions that model
* the individual instances in the
* set of instances represented by the given instance key.
* @requires key in this.info.relevantClasses()
* @return an iterator over the expressions that model
* the individual instances in the
* set of instances represented by the given instance key.
*/
public final Iterator<Expression> instances(InstanceKey key) {
return Containers.iterate(instances.get(key));
}
/**
* Returns an expression that models the set of all instances of the given type.
* @return an expression that models the set of all instances of the given type.
*/
public final Expression valueOf(TypeReference type) {
final List<Expression> parts = new ArrayList<Expression>();
for(InstanceKey key : sets.keySet()) {
IClassHierarchy cha = key.getConcreteType().getClassHierarchy();
if (cha.isSubclassOf(key.getConcreteType(), cha.lookupClass(type))) {
parts.add(sets.get(key));
}
}
return parts.isEmpty() ? Expression.NONE : Expression.union(parts);
}
/**
* Returns an expression that models the set of instances represented by the given instance key.
* @requires key in this.info.relevantClasses()
* @return this.instances[key]
*/
public final Expression valueOf(InstanceKey key) { return sets.get(key); }
/**
* Returns an expression that models the given string constant.
* @return this.primitives[s]
*/
public final Expression valueOf(String s) { return NULL; }
/**
* Returns a formula that models the given boolean constant.
* @return this.primitives[b]
*/
public final Formula valueOf(boolean b) { return b ? Formula.TRUE : Formula.FALSE; }
/**
* Returns an integer expression that models the given integer constant.
* @return this.primitives[i]
*/
public final IntExpression valueOf(int i) { return IntConstant.constant(i); }
/**
* Returns an integer expression that models the given integer constant.
* @return this.primitives[i]
*/
public final IntExpression valueOf(long i) {
assert Integer.MIN_VALUE <= i;
assert i <= Integer.MAX_VALUE;
return IntConstant.constant((int)i);
}
/**
* Returns a real expression that models the given float constant.
* @return this.primitives[f]
*/
public final RealExpression valueOf(float f) { return new RealExpression(f); }
/**
* Returns an empty PhiExpression of the given type.
* @return { phi: PhiExpression | no phi.phis and
* [[ phi.phis[Formula] ]] in [[ type ]] }
*/
@SuppressWarnings("unchecked")
public <T> PhiExpression<T> valuePhi(IRType type) {
return PhiExpression.valuePhi((Interpreter<T>)interpreter(type));
}
/**
* Returns the default value for the given type.
* @return default value for the given type.
*/
@SuppressWarnings("unchecked")
public <T> T defaultValue(IRType type) {
return ((Interpreter<T>)interpreter(type)).defaultValue();
}
/**
* Returns the interpreter for the given type. The
* type of the returned interpreter is Interpreter<Expression> for
* OBJECT, Interpreter<Formula> for BOOLEAN, NumberInterpreter<IntExpression> for
* INTEGER, and NumberInterpreter<RealExpression> for REAL.
* @return this.interpreters[type]
*/
@SuppressWarnings("unchecked")
public final <T> Interpreter<T> interpreter(IRType type) {
switch(type) {
case OBJECT : return (Interpreter<T>)objects;
case BOOLEAN : return (Interpreter<T>)bools;
case INTEGER : return (Interpreter<T>)ints;
case REAL : return (Interpreter<T>)reals;
default : throw new IllegalArgumentException("Unknown type: " + type);
}
}
/**
* Returns this factory's interpreter for integers.
* @return this.interpreters[INTEGER]
*/
public final Interpreter<IntExpression> intInterpreter() { return ints; }
/**
* Returns this factory's interpreter for objects.
* @return this.interpreters[OBJECT]
*/
public final Interpreter<Expression> objInterpreter() { return objects; }
/**
* Returns this factory's interpreter for reals.
* @return this.interpreters[REAL]
*/
public final Interpreter<RealExpression> realInterpreter() { return reals; }
/**
* Returns this factory's interpreter for booleans.
* @return this.interpreters[BOOLEAN]
*/
public final Interpreter<Formula> boolInterpreter() { return bools; }
/**
* Returns a string representation of this value factory.
* @return a string representation of this value factory
*/
public String toString() {
final StringBuilder buf = new StringBuilder();
buf.append("instances:\n");
for(Map.Entry<InstanceKey,Relation[]> e : instances.entrySet()) {
buf.append(" ");
buf.append(e.getKey());
buf.append(" :: {");
for(Relation r : e.getValue()) {
buf.append(" ");
buf.append(r);
}
buf.append(" }\n");
}
return buf.toString();
}
/*------------------ STATIC METHODS ------------------ */
/** @return an interpreter for boolean values. */
private static Interpreter<Formula> bools() {
return new Interpreter<Formula>() {
public IRType type() { return BOOLEAN; }
public Expression toObj(Formula f) { return f.thenElse(TRUE, FALSE); }
public Formula fromObj(Expression e) { return e.eq(TRUE); }
public Expression defaultObj() { return FALSE; }
public Formula guardedValue(Formula guard, Formula value) { return guard.and(value); }
public Formula defaultValue() { return Formula.FALSE; }
public Boolean evaluate(Formula value, Evaluator eval) { return eval.evaluate(value); }
public boolean singletonEncoding() { return true; }
Formula phi(Collection<? extends Formula> phis) {
assert !phis.isEmpty();
return Formula.or(phis);
}
};
}
/** @return an interpreter for integer values. */
private static Interpreter<IntExpression> ints() {
final IntConstant zero = IntConstant.constant(0);
return new Interpreter<IntExpression>() {
public IRType type() { return INTEGER; }
public Expression toObj(IntExpression intExpr) { return intExpr.toBitset(); }
public IntExpression fromObj(Expression e) { return e.sum(); }
public Expression defaultObj() { return Expression.NONE; }
public IntExpression guardedValue(Formula guard, IntExpression value) { return guard.thenElse(value, zero); }
public IntExpression defaultValue() { return zero; }
public Integer evaluate(IntExpression value, Evaluator eval) { return eval.evaluate(value); }
public boolean singletonEncoding() { return false; }
IntExpression phi(Collection<? extends IntExpression> phis) {
assert !phis.isEmpty();
return IntExpression.or(phis);
}
};
}
/** @return an interpreter for reference values. */
private static Interpreter<Expression> objects() {
return new Interpreter<Expression>() {
public IRType type() { return OBJECT; }
public Expression toObj(Expression t) { return t; }
public Expression fromObj(Expression e) { return e; }
public Expression defaultObj() { return NULL; }
public Expression guardedValue(Formula guard, Expression value) {
return guard.thenElse(value, Expression.NONE);
}
public Expression defaultValue() { return NULL; }
public Object evaluate(Expression value, Evaluator eval) {
final TupleSet v = eval.evaluate(value);
assert v.size() <= 1;
assert v.arity() == 1;
return v.isEmpty() ? "()" : v.iterator().next().atom(0);
}
public boolean singletonEncoding() { return true; }
Expression phi(Collection<? extends Expression> phis) {
assert !phis.isEmpty();
return Expression.union(phis);
}
};
}
}