/****************************************************************************** * 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.translation.concurrent; import static com.ibm.wala.memsat.util.Graphs.root; import static com.ibm.wala.memsat.util.Nodes.tupleset; import static com.ibm.wala.memsat.util.Programs.instructions; import static com.ibm.wala.memsat.util.Programs.referencedArray; import static com.ibm.wala.memsat.util.Programs.referencedFields; import static com.ibm.wala.memsat.util.Programs.referencedInstance; import static com.ibm.wala.memsat.util.Programs.referencedMonitor; import static com.ibm.wala.memsat.util.Programs.thread; import static com.ibm.wala.memsat.util.Programs.valueFor; import static com.ibm.wala.memsat.util.Strings.fieldNames; import static com.ibm.wala.memsat.util.Strings.instructionNames; import java.util.Collections; import java.util.EnumMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import com.ibm.wala.classLoader.IField; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; import com.ibm.wala.ipa.cha.IClassHierarchy; import com.ibm.wala.memsat.frontEnd.IRType; import com.ibm.wala.memsat.frontEnd.InlinedInstruction; import com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action; import com.ibm.wala.memsat.frontEnd.WalaConcurrentInformation; import com.ibm.wala.memsat.frontEnd.WalaInformation; import com.ibm.wala.memsat.representation.ConstantFactory; import com.ibm.wala.memsat.representation.ExpressionFactory; import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; import com.ibm.wala.ssa.SSAArrayReferenceInstruction; import com.ibm.wala.ssa.SSAFieldAccessInstruction; import com.ibm.wala.ssa.SSAInstruction; import com.ibm.wala.util.graph.Graph; import com.ibm.wala.util.graph.traverse.DFS; import kodkod.ast.Expression; import kodkod.ast.Formula; import kodkod.ast.Relation; import kodkod.instance.Bounds; import kodkod.instance.TupleFactory; import kodkod.instance.TupleSet; import kodkod.util.collections.Containers; /** * A factory for generating atoms that represent actions that may be * performed by a given concurrent program and for generating * atoms and expressions corresponding to locations / monitors that may be * read / written / locked / unlocked by that program. * * @specfield base: ExpressionFactory * @specfield insts: set InlinedInstruction * @specfield fields: insts ->lone IField * @specfield exprs: (Action + insts.fields) ->one Relation * @specfield threads, thread: Expression // expressions representing all threads in base.info and the mapping from actions to those threads * @invariant insts = { inst: InlinedInstruction | some n: nodes(base.info.threads()) | base.info.concurrentInformation(n).actions().contains(inst) } * @invariant fields = { i: insts, f: IField | i.instruction instanceof SSAFieldAccessInstruction && f = base.info.callGraph().getClassHierarchy().resolveField( i.getDeclaredField() ) } * @invariant base.info.threads.getNumberOfNodes() > 1 * @author etorlak */ final class ConcurrentFactory { private final ExpressionFactory base; private final Map<InlinedInstruction, Set<?>> instAtoms; private final Map<InlinedInstruction, IField> instFields; private final Map<IField, Relation> fieldExprs; private final Map<Action, Relation> actExprs; private static final boolean DEBUG = true; /** * Constructs a new action factory using the given base expression factory. * @requires base.options.memoryModel != null * @effects this.base' = base */ public ConcurrentFactory(ExpressionFactory base) { this.base = base; this.actExprs = new EnumMap<Action, Relation>(Action.class); for(Action a : Action.values()) { this.actExprs.put(a, Relation.unary(a.toString())); } this.instAtoms = new LinkedHashMap<InlinedInstruction, Set<?>>(); if (base.options().memoryModel().usesSpeculation()) { initSpeculativeAtoms(); } else { initNonSpeculativeAtoms(); } // System.out.println("INSTRUCTION ATOMS: "); // // for(InlinedInstruction inst : instAtoms.keySet()) { // System.out.println("[[" + System.identityHashCode(inst) + ", " + inst.hashCode() + "]] " + inst + " = " + instAtoms.get(inst)); // } this.instFields = referencedFields(base.info()); this.fieldExprs = new LinkedHashMap<IField, Relation>(); for(Map.Entry<IField, String> named : fieldNames(new LinkedHashSet<IField>(instFields.values())).entrySet()) { fieldExprs.put( named.getKey(), Relation.unary(named.getValue()) ); } } /** Initializes the mapping from instructions to sets of atoms for a non-speculative memory model. */ private void initNonSpeculativeAtoms() { for(Map.Entry<InlinedInstruction,String> named : instructionNames(instructions(base.info())).entrySet()) { instAtoms.put(named.getKey(), Collections.singleton(new ActionAtom(named.getValue()))); } } /** Initializes the mapping from instructions to sets of atoms for a speculative memory model. */ private void initSpeculativeAtoms() { final WalaInformation info = base.info(); final Graph<CGNode> threads = info.threads(); if (DEBUG) System.out.println(threads); int atomIdx = 0; for(Iterator<CGNode> tItr = DFS.iterateDiscoverTime(threads, root(threads)); tItr.hasNext(); ) { final CGNode thread = tItr.next(); final WalaConcurrentInformation tInfo = info.concurrentInformation(thread); final Map<InlinedInstruction, Effects> effects = effects(tInfo); final Map<Effects, Set<InlinedInstruction>> maxEffects = maxEffects(tInfo, effects); if (DEBUG) print(tInfo, effects, maxEffects); // allocate atoms in depth-first order to get a nice numbering final Map<InlinedInstruction, ActionAtom> atoms = new LinkedHashMap<InlinedInstruction, ActionAtom>(); final Graph<InlinedInstruction> to = tInfo.threadOrder(); for(Iterator<InlinedInstruction> insts = DFS.iterateDiscoverTime(to, tInfo.start()); insts.hasNext(); ) { final InlinedInstruction inst = insts.next(); if (maxEffects.get(effects.get(inst)).contains(inst)) { atoms.put(inst, new ActionAtom(atomIdx++)); } } // compute the upper bound B on the actions for each instruction i as follows: // for each instruction r such that atoms.containsKey(r) and effects.get(i).overlaps(effects.get(r)), // add atoms.get(r) to the upper bound B of i for(Iterator<InlinedInstruction> insts = DFS.iterateDiscoverTime(to, tInfo.start()); insts.hasNext(); ) { final InlinedInstruction inst = insts.next(); final Effects instEffects = effects.get(inst); final Set<ActionAtom> upper = new LinkedHashSet<ActionAtom>(4); //if (atoms.containsKey(inst)) { upper.add(atoms.get(inst)); } for(InlinedInstruction rep : atoms.keySet()) { if (instEffects.overlaps(effects.get(rep))) upper.add(atoms.get(rep)); } assert !upper.isEmpty(); instAtoms.put(inst, upper); } } for(Map.Entry<InlinedInstruction, Set<?>> entry : instAtoms.entrySet()) { System.out.println(entry); } // throw new AssertionError(); } /** @effects prints debug info to standard out */ private void print(WalaConcurrentInformation tInfo, Map<InlinedInstruction, Effects> effects, Map<Effects, Set<InlinedInstruction>> maxEffects) { System.out.println("\n*****WALA REPRESENTATION OF " + tInfo.root().getMethod().getSignature() + "*****"); System.out.println(tInfo); final Map<Effects,Set<InlinedInstruction>> equivalences = new LinkedHashMap<Effects, Set<InlinedInstruction>>(); for(Map.Entry<InlinedInstruction, Effects> entry : effects.entrySet()) { Set<InlinedInstruction> insts = equivalences.get(entry.getValue()); if (insts==null) { insts = new LinkedHashSet<InlinedInstruction>(); equivalences.put(entry.getValue(), insts); } insts.add(entry.getKey()); } System.out.println("\n*****EQUIVALENCE CLASSES OF " + tInfo.root().getMethod().getSignature() + "*****"); for(Map.Entry<Effects, Set<InlinedInstruction>> eq : equivalences.entrySet()) { System.out.println(" CLASS ="); System.out.println(" members: " + eq.getValue()); System.out.println(" maximal path: " + maxEffects.get(eq.getKey())); } } /** * Returns a map from each instruction in tInfo.actions to an Effects object that represents * the effects that it may have when executed. * @requires some n: this.base.info.thread.nodes | this.base.info.concurrentInformation(n) = tInfo * @return a map from each instruction in tInfo.actions to an Effects object that represents * the effects that it may have when executed. */ private final Map<InlinedInstruction, Effects> effects(WalaConcurrentInformation tInfo) { final Map<InlinedInstruction, Effects> effects = new LinkedHashMap<InlinedInstruction, Effects>(); final Map<Effects, Effects> cache = new LinkedHashMap<Effects, Effects>(); for(InlinedInstruction inst : tInfo.actions()) { final Effects instEffects = new Effects(base.info(), inst); final Effects cached = cache.get(instEffects); if (cached==null) { effects.put(inst, instEffects); cache.put(instEffects, instEffects); } else { effects.put(inst, cached); } } return effects; } /** * Returns a map from each E in effectEquivalences.values to the subset S of instructions * mapped to E such that: (1) there is a path in tInfo.threadOrder that contains all of the * instructions in S, and (2) there is no path in tInfo.threadOrder that contains a subset * of instructions mapped to E that is larger than S. * @requires effects = effects(tInfo) * @return a map, as described above */ private final Map<Effects, Set<InlinedInstruction>> maxEffects(WalaConcurrentInformation tInfo, Map<InlinedInstruction, Effects> effects) { final Map<InlinedInstruction, Map<Effects, Set<InlinedInstruction>>> results = new LinkedHashMap<InlinedInstruction, Map<Effects, Set<InlinedInstruction>>>(); final Graph<InlinedInstruction> threadOrder = tInfo.threadOrder(); for(Iterator<InlinedInstruction> insts = DFS.iterateFinishTime(threadOrder, Containers.iterate(tInfo.start())); insts.hasNext(); ) { final InlinedInstruction inst = insts.next(); final Effects effect = effects.get(inst); final Map<Effects, Set<InlinedInstruction>> result = new LinkedHashMap<Effects, Set<InlinedInstruction>>(); for(Iterator<? extends InlinedInstruction> itr = threadOrder.getSuccNodes(inst); itr.hasNext(); ) { final Map<Effects, Set<InlinedInstruction>> succ = results.get(itr.next()); for(Map.Entry<Effects, Set<InlinedInstruction>> succEntry : succ.entrySet()) { final Effects succEq = succEntry.getKey(); final Set<InlinedInstruction> succInsts = succEntry.getValue(); final Set<InlinedInstruction> currInsts = result.get(succEq); if (currInsts==null || currInsts.size()<succInsts.size()) result.put(succEq, succInsts); } } if (result.containsKey(effect)) { final Set<InlinedInstruction> currInsts = new LinkedHashSet<InlinedInstruction>(result.get(effect)); currInsts.add(inst); result.put(effect, currInsts); } else { result.put(effect, Collections.singleton(inst)); } results.put(inst, result); } return results.get(tInfo.start()); } /** * Returns the base expression factory. * @return this.base */ public final ExpressionFactory base() { return base; } /** * Returns the constant unary expression that evaluates to all atoms of the given kind. * @return this.exprs[kind] */ public Expression valueOf(Action kind) { return actExprs.get(kind); } /** * Returns the singleton unary expression that represents the field that is * accessed by the given read or write instruction. * @requires inst in this.instructions * @requires inst.instruction instanceof SSAFieldAccessInstruction * @return this.exprs[this.fields[inst]] */ public Expression fieldOf(InlinedInstruction inst) { return fieldExprs.get(instFields.get(inst)); } /** * Returns true if the upper bounds on the actions executable by inst1 and inst2 * (as given by {@linkplain #actionAtoms(TupleFactory, InlinedInstruction)} } intersect. * @requires inst1 + inst2 in this.insts * @return true if the upper bounds on the actions executable by inst1 and inst2 intersect. */ public final boolean mayShareActions(InlinedInstruction inst1, InlinedInstruction inst2) { return intersects( instAtoms.get(inst1), instAtoms.get(inst2) ); } /** * Returns a tupleset containing all actions that the given instruction may perform. * @requires inst in this.insts * @requires this.atoms() in factory.universe.atoms[int] * @return tupleset all actions that the given instruction may perform. */ public final TupleSet actionAtoms(TupleFactory tuples, InlinedInstruction inst) { if (instAtoms.get(inst)==null) System.out.println("\nNO ACTION FOR " /*+ "[[" + System.identityHashCode(inst) + ", " + inst.hashCode() + "]] "*/ + inst); return tupleset(tuples, instAtoms.get(inst)); } /** * Returns a tupleset containing the set of atoms that describe the locations * that the given read/write instruction may access. * @requires inst in this.insts * @requires this.atoms() in factory.universe.atoms[int] * @requires inst.action in NORMAL_READ + NORMAL_WRITE + VOLATILE_READ + VOLATILE_WRITE * @return a tupleset containing the set of atoms that describe the locations * that the given read/write instruction may access. */ public final TupleSet locationAtoms(TupleFactory tuples, InlinedInstruction inst) { final SSAInstruction obj = inst.instruction(); final TupleSet loc = tuples.noneOf(1); final ConstantFactory constants = base.constants(); if (obj instanceof SSAFieldAccessInstruction) { loc.add( tuples.tuple(fieldExprs.get(instFields.get(inst))) ); final Set<InstanceKey> ref = referencedInstance(base.info(), inst); if (!ref.isEmpty()) { loc.addAll( constants.instanceAtoms(tuples, ref)) ; } } else { assert obj instanceof SSAArrayReferenceInstruction; final Set<InstanceKey> ref = referencedArray(base.info(), inst); loc.addAll( constants.instanceAtoms(tuples, ref) ); loc.addAll( constants.constantAtoms(tuples, IRType.INTEGER) ); } return loc; } /** * Returns a tupleset containing the set of atoms that describe the monitors that the * given lock/unlock instruction may access. * @requires inst in this.insts * @requires this.atoms() in factory.universe.atoms[int] * @requires inst.action in LOCK + UNLOCK * @return a tupleset containing the set of atoms that describe the monitors that the * given lock/unlock instruction may access. */ public final TupleSet monitorAtoms(TupleFactory tuples, InlinedInstruction inst) { return base.constants().instanceAtoms(tuples, referencedMonitor(base.info(), inst)); } /** * Returns a tupleset containing the set of atoms that describe the values that the * given read/write instruction may read or write. * @requires inst in this.insts * @requires this.atoms() in factory.universe.atoms[int] * @requires inst.action in NORMAL_READ + NORMAL_WRITE + VOLATILE_READ + VOLATILE_WRITE * @return a tupleset containing the set of atoms that describe the values that the * given read/write instruction may read or write. */ public final TupleSet valueAtoms(TupleFactory tuples, InlinedInstruction inst) { final ConstantFactory constants = base.constants(); final Set<InstanceKey> rangeKeys = valueFor(base.info(), inst); if (!rangeKeys.isEmpty()) { final TupleSet val = constants.instanceAtoms(tuples, rangeKeys); val.add(tuples.tuple(constants.nil())); return val; } else { final IRType type; final SSAInstruction delegate = inst.instruction(); if (delegate instanceof SSAFieldAccessInstruction) { type = IRType.convert(((SSAFieldAccessInstruction) delegate).getDeclaredFieldType()); } else { type = IRType.convert(((SSAArrayReferenceInstruction) delegate).getElementType()); } if (type.equals( IRType.OBJECT )) { // empty range for object type, which must // mean these objects are never created. thus // null is the only valid object return tuples.setOf(constants.nil()); } else { return constants.constantAtoms(tuples, type); } } } /** * Returns an ordered set of atoms needed to represent all actions * and fields generated by this factory and this.base. * @return an ordered set of atoms needed to represent all actions * and fields generated by this factory and this.base */ public final Set<?> atoms() { final Set<Object> atoms = new LinkedHashSet<Object>(base.atoms()); for(Set<?> s : instAtoms.values()) { atoms.addAll(s); } atoms.addAll(fieldExprs.values()); return atoms; } /** * Updates the given Bounds instance with * upper/lower bounds for the relations that make up * the expressions generated by this factory and this.base. * @requires this.atoms() in bounds.universe.atoms[int] * @effects updates the given Bounds instance with * upper/lower bounds for the relations that make up * the expressions generated by this factory and this.base. */ public void boundAll(Bounds bounds) { base.boundAll(bounds); final TupleFactory tuples = bounds.universe().factory(); final Map<Action,TupleSet> actBounds = new EnumMap<Action, TupleSet>(Action.class); for(Action a : Action.values()) { actBounds.put(a, tuples.noneOf(1)); } for(Map.Entry<InlinedInstruction, Set<?>> entry : instAtoms.entrySet()) { actBounds.get(entry.getKey().action()).addAll(tupleset(tuples, entry.getValue())); } for(Map.Entry<Action, TupleSet> entry : actBounds.entrySet()) { bounds.boundExactly(actExprs.get(entry.getKey()), entry.getValue()); } for(Map.Entry<IField, Relation> entry : fieldExprs.entrySet()) { bounds.boundExactly(entry.getValue(), tuples.setOf(entry.getValue())); } } /** * Returns a formula that expresses the representation * invariants over all Kodkod relations that make up * the expressions generated by this factory and this.base. * @return a formula that expresses the representation * invariants over all Kodkod relations that make up * the expressions generated in this factory and this.base. */ public Formula invariants() { return base.invariants(); } /** * Returns true if s1 and s2 intersect. Otherwise returns false. * @return true if s1 and s2 intersect. Otherwise returns false. */ private static boolean intersects(Set<?> s1, Set<?> s2) { final Set<?> small, big; if (s1.size()<=s2.size()) { small = s1; big = s2; } else { small = s2; big = s1; } for(Object o : small) { if (big.contains(o)) return true; } return false; } /** * An object that represents a particular action. * @author etorlak */ private static final class ActionAtom { private final String name; ActionAtom(int idx) { this.name = "a"+idx; } ActionAtom(String name) { this.name = name; } public String toString() { return name; } } /** * Describes the entities (objects, fields, methods, etc) that may be affected * when a given instruction is executed. * @specfield info: WalaInformation * @specfield thread: info.threads.nodes // thread that contains the instruction * @specfield instruction: info.concurrentInformation(thread).actions // the instruction * @specfield action: Action // the kind of action performed by the instruction * @specfield location: Object // affected "location": field, method reference, or cg node * @specfield instances: set InstanceKey // affected objects * @author etorlak */ private static final class Effects { final CGNode thread; final Action action; final Object location; final Set<InstanceKey> instances; /** * Constructs an effects descriptor for the given instruction. * In particular, a field access instruction is mapped to the field that is accessed and to the set of InstanceKeys * representing the objects whose field may be being read/written. An array access instruction is mapped to its thread * and to the set of InstanceKeys representing the array objects that may be read/written. A monitor instruction is mapped * to its cgNode and to the set of InstanceKeys representing the objects that may be locked/unlocked. A special instruction * is mapped to the method reference describing the invoked method and to the empty set. Start/end instructions are mapped to * their thread and to the empty set. * @effects this.instruction' = inst */ Effects(WalaInformation info, InlinedInstruction inst) { final IClassHierarchy cha = info.callGraph().getClassHierarchy(); this.action = inst.action(); this.thread = thread(inst); switch(action) { case START : case END : location = thread; instances = Collections.emptySet(); break; case SPECIAL : location = cha.resolveMethod(((SSAAbstractInvokeInstruction)inst.instruction()).getDeclaredTarget()).getReference(); instances = Collections.emptySet(); break; case LOCK : case UNLOCK : location = inst.cgNode(); instances = referencedMonitor(info, inst); break; case NORMAL_READ : case VOLATILE_READ : case NORMAL_WRITE : case VOLATILE_WRITE : if (inst.instruction() instanceof SSAFieldAccessInstruction) { location = cha.resolveField(((SSAFieldAccessInstruction)inst.instruction()).getDeclaredField()); instances = referencedInstance(info, inst); } else { assert inst.instruction() instanceof SSAArrayReferenceInstruction; location = thread; instances = referencedArray(info, inst); } break; default : throw new AssertionError("unreachable"); } } /** * Returns true if the effects of this.instruction may overlap with the effects of other.instruction. * @return true if the effects of this.instruction may overlap with the effects of other.instruction. */ boolean overlaps(Effects other) { if (this==other) return true; else return action.equals(other.action) && thread.equals(other.thread) && location.equals(other.location) && ((instances.isEmpty() && other.instances.isEmpty()) || intersects(instances, other.instances)); } /** * {@inheritDoc} * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + action.hashCode(); result = prime * result + instances.hashCode(); result = prime * result + location.hashCode(); result = prime * result + thread.hashCode(); return result; } /** * {@inheritDoc} * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final Effects other = (Effects) obj; return action.equals(other.action) && thread.equals(other.thread) && location.equals(other.location) && instances.equals(other.instances); } } }