/******************************************************************************
* 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.concurrent.memory.jmm;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.END;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.LOCK;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.NORMAL_READ;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.NORMAL_WRITE;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.START;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.UNLOCK;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.VOLATILE_READ;
import static com.ibm.wala.memsat.frontEnd.InlinedInstruction.Action.VOLATILE_WRITE;
import static com.ibm.wala.memsat.util.Graphs.root;
import static com.ibm.wala.memsat.util.Nodes.totalOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.ibm.wala.ipa.callgraph.CGNode;
import com.ibm.wala.memsat.concurrent.Program;
import com.ibm.wala.memsat.concurrent.memory.AbstractExecution;
import com.ibm.wala.memsat.frontEnd.InlinedInstruction;
import com.ibm.wala.memsat.frontEnd.WalaConcurrentInformation;
import com.ibm.wala.memsat.frontEnd.WalaInformation;
import com.ibm.wala.memsat.util.Graphs;
import com.ibm.wala.memsat.util.Nodes;
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.ast.Variable;
/**
* An execution specific to the JMM, based on
* <ol>
* <li> J.�Sevc�k and D.�Aspinall. On validity of program transformations in
* the Java memory model. In ECOOP �08, volume 5142 of LNCS, pages 27�51. Springer Berlin, 2008. </li>
* </ol>
*
* @specfield prog: Program
* @specfield mem: MemoryModel
* @specfield actions, po, so, hb, sw, v, w: Expression
*
* @author etorlak
*/
public final class JMMExecution extends AbstractExecution {
private final Relation so;
private final Expression po, sw, hb;
private final Formula wellFormed;
private final boolean speculative;
/**
* Creates a new JMMExecution for the given program, using the given speculation
* flag and the given suffix in the names of all relations it allocates.
*/
private JMMExecution(Program prog, boolean speculative, String suffix) {
super(prog, suffix.length() > 0 ? "_" + suffix : suffix);
this.speculative = speculative;
this.po = computePO(prog);//Relation.binary("po"+suffix);
this.so = Relation.binary("so"+suffix);
this.sw = computeSW(prog);
this.hb = po.union(sw).closure();
this.wellFormed = computeWellFormed(prog);
}
/**
* Computes the program order expression for this execution of the given program.
* @return po for this execution the given program
*/
private final Expression computePO(Program p) {
final List<Expression> ecfg = new ArrayList<Expression>();
final WalaInformation info = p.info();
for(Iterator<CGNode> nodes = DFS.iterateDiscoverTime(info.threads(), root(info.threads())); nodes.hasNext(); ) {
final WalaConcurrentInformation tInfo = info.concurrentInformation(nodes.next());
final Graph<InlinedInstruction> to = tInfo.threadOrder();
for(Iterator<InlinedInstruction> insts = DFS.iterateDiscoverTime(to, tInfo.start()); insts.hasNext(); ) {
final InlinedInstruction inst = insts.next();
final Relation action = action(inst);
ecfg.add(action.product(action));
for(Iterator<? extends InlinedInstruction> succs = to.getSuccNodes(inst); succs.hasNext(); ) {
ecfg.add( action.product(action(succs.next())) );
}
}
}
return Expression.union(ecfg).closure();
}
/**
* Computes the synchronizes-with expression for this execution of the given program.
* @return sw for this execution the given program
*/
private final Expression computeSW(Program p) {
final Variable a = Variable.unary("a"), b = Variable.unary("b");
final Formula synced = a.product(b).in(so);
final Expression swLocks = synced.and(monitorOf(a).eq(monitorOf(b))).
comprehension(a.oneOf(p.allOf(UNLOCK)).and(b.oneOf(p.allOf(LOCK))));
final Expression swVolatiles = synced.and(locationOf(a).eq(locationOf(b))).
comprehension(a.oneOf(p.allOf(VOLATILE_WRITE)).and(b.oneOf(p.allOf(VOLATILE_READ))));
final Expression swThreads = synced.and(p.threadOf(a).product(p.threadOf(b)).in(p.endsBefore())).
comprehension(a.oneOf(p.allOf(END)).and(b.oneOf(p.allOf(START))));
return Expression.union(swLocks, swVolatiles, swThreads);
}
/**
* Computes the well-formedness constraints for this execution of the given program.
* @return well-formedness constraints for this execution of the given program
*/
private final Formula computeWellFormed(Program p) {
final List<Formula> wf = new ArrayList<Formula>(15);
final Expression threads = p.threads(Graphs.nodes(p.info().threads()));
final Expression actions = actions();
final Expression reads = actions.intersection(p.allOf(NORMAL_READ, VOLATILE_READ));
final Expression volatileReads = actions.intersection(p.allOf(VOLATILE_READ));
final Expression writes = actions.intersection(p.allOf(NORMAL_WRITE, VOLATILE_WRITE));
final Expression volatileWrites = actions.intersection(p.allOf(VOLATILE_WRITE));
final Expression locks = actions.intersection(p.allOf(LOCK));
final Expression syncs = actions.intersection(p.allOf(VOLATILE_READ, VOLATILE_WRITE, LOCK, UNLOCK, START, END));
// Def 6.3-6.4: all actions in a thread happen after the thread start and before the thread end actions
// No need for explicit constraint: enforced by Program bounds.
// Def 6.5: initialization thread contains only start, end and writes
// No need for explicit constraint: see WalaInformation.concurrentInformation
// Def 7.2: po restricted to the actions of one thread is a total order and
// does not relate actions of different threads
// all t: this.threads | totalOrder[this.po, t.actions & this.actions]
// all a, b: this.actions | a->b in this.po => a.thread = b.thread
final Variable t = Variable.unary("t");
final Variable a = Variable.unary("a"), b = Variable.unary("b");
// wf.add( totalOrder(po, actions.intersection(p.actionsOf(t))).forAll(t.oneOf(threads)) );
// wf.add( a.product(b).in(po).implies(p.threadOf(a).eq(p.threadOf(b))).forAll(a.oneOf(actions).and(b.oneOf(actions))) );
// po is consistent with the program text
// final WalaInformation info = p.info();
// for(Iterator<CGNode> nodes = DFS.iterateDiscoverTime(info.threads(), root(info.threads())); nodes.hasNext(); ) {
// final WalaConcurrentInformation tInfo = info.concurrentInformation(nodes.next());
// final Graph<InlinedInstruction> to = tInfo.threadOrder();
// for(Iterator<InlinedInstruction> insts = DFS.iterateDiscoverTime(to, tInfo.start()); insts.hasNext(); ) {
// final InlinedInstruction inst = insts.next();
// final Relation action = action(inst);
// for(Iterator<? extends InlinedInstruction> succs = to.getSuccNodes(inst); succs.hasNext(); ) {
// wf.add( action.product(action(succs.next())).in(po) );
// }
// }
// }
// Def 7.3: so is a total order on executed synchronization actions
// totalOrder[this.so, syncs]
wf.add( totalOrder(so, syncs) );
// Def 7.4: so is consistent with po
// all a, b: this.actions | (a->b in this.so and b->a in this.po) => a = b
wf.add( a.product(b).in(so).and(b.product(a).in(po)).implies(a.eq(b)).forAll(a.oneOf(actions).and(b.oneOf(actions))) );
// Def 7.5: the write-seen must be at the same location as the read;
// this constraint, in conjunction with "volatility" being associated with locations rather than memory accesses,
// ensures that the rule D6.5 holds (i.e. W is properly typed: for every non-volatile read r, W(r) is non-volatile,
// and for every volatile read r, W(r) is volatile)
// all r: this.reads | this.W[r].location = r.location and this.W[r] in this.actions and one this.W[r]
final Variable r = Variable.unary("r");
wf.add( Formula.and(locationOf(w(r)).eq(locationOf(r)), w(r).in(actions), w(r).one()).forAll(r.oneOf(reads)) );
// Def 7.6: locking is proper; for all lock actions l in A on monitors m and all threads t different
// from the thread of l, the number of locks in t before l in so is the same as the number of unlocks in t before l in so.
// all l: this.locks, t: this.threads - l.thread | let prevSyncs = (t.actions & (l.monitor).~monitor & ^(this.so).l) |
// #(prevSyncs & Lock) = #(prevSyncs & Unlock)
final Variable l = Variable.unary("l");
final Expression prevSyncs = Expression.intersection(so.closure().join(l), syncsOn(monitorOf(l)), p.actionsOf(t));
wf.add( prevSyncs.intersection(p.allOf(LOCK)).count().eq(prevSyncs.intersection(p.allOf(UNLOCK)).count()).
forAll(l.oneOf(locks).and(t.oneOf(threads.difference(p.threadOf(l))))) );
// Def 7.8: so is consistent with W; for every volatile read r of a variable v we have W(r) <=_{so} r
// and for any volatile write w to v, either w <=_{so} W(r) or r <=_{so} w.
// all r: this.actions & Volatile.reads, w: this.actions & Volatile.writes |
// r.location=w.location => (this.W[r] -> r in this.so and (w -> this.W[r] in this.so or r -> w in this.so))
final Variable w = Variable.unary("w");
wf.add( locationOf(r).eq(locationOf(w)).implies(
w(r).product(r).in(so).and(w.product(w(r)).in(so).or(r.product(w).in(so)))).
forAll(r.oneOf(volatileReads).and(w.oneOf(volatileWrites))) );
// Def 7.9: hb is consistent with W; for all reads r of v it holds that r !<=_{hb} W(r) and
// there is no intervening write w to v, i.e. if W(r) <=_{hb} w <=_{hb} r and w writes to v then W(r) = w.
// all r: this.reads, w: this.writes |
// r.location=w.location =>
// (r->this.W[r] !in this.hb and ((this.W[r] -> w in this.hb and w -> r in this.hb) => this.W[r] = w))
wf.add( locationOf(r).eq(locationOf(w)).implies(
r.product(w(r)).in(hb).not().and(w(r).product(w).in(hb).and(w.product(r).in(hb)).implies(w(r).eq(w)))).
forAll(r.oneOf(reads).and(w.oneOf(writes))) );
// Def 7.10: the initialization thread finishes before any other thread starts
// No need for explicit constraint: enforced by Program bounds.
// Def 7.7: program order is intra-thread consistent (sequentially valid)
wf.add( p.sequentiallyValid(this) );
return Formula.and(wf);
}
/**
* Returns a main (non-speculative) execution for the given program.
* @return a main (non-speculative) execution for the given program.
*/
static JMMExecution main(Program prog) {
return new JMMExecution(prog, false, "");
}
/**
* Returns a list of the given size, initialized with fresh speculative
* executions of the given program.
* @return a list of the given size, initialized with fresh speculative
* executions of the given program.
*/
static List<JMMExecution> speculative(Program prog, int size) {
final List<JMMExecution> speculations = new ArrayList<JMMExecution>(size);
for(int i = 0; i < size; i++) {
speculations.add( new JMMExecution(prog, true, String.valueOf(i)) );
}
return speculations;
}
/**
* {@inheritDoc}
* @see com.ibm.wala.memsat.concurrent.Execution#isSpeculative()
*/
public boolean isSpeculative() { return speculative; }
/**
* {@inheritDoc}
* @see com.ibm.wala.memsat.concurrent.Execution#po()
*/
public Expression po() { return po; }
/**
* Returns the synchronization order relation for this execution.
* @return this.so
*/
public Relation so() { return so; }
/**
* {@inheritDoc}
* @see com.ibm.wala.memsat.concurrent.Execution#hb()
*/
public Expression hb() { return hb; }
/**
* Returns the synchronizes-with relation for this execution.
* @return this.sw
*/
public Expression sw() { return sw; }
/**
* Returns a formula that evaluates to true iff this execution is well-formed
* with respect to this.prog.
* @return a formula that evaluates to true iff this execution is well-formed
* with respect to this.prog.
*/
public Formula wellFormed() { return wellFormed; }
/**
* {@inheritDoc}
* @see com.ibm.wala.memsat.concurrent.Execution#viz()
*/
public Map<Expression, String> viz() {
return Collections.singletonMap(Nodes.transitiveReduction(hb).difference(Expression.IDEN), "happensBefore");
}
}