/*
* Copyright (C) 2018 Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* Copyright (C) 2018 Lars Nitzke (lars.nitzke@outlook.com)
* Copyright (C) 2018 University of Freiburg
*
* This file is part of the ULTIMATE ModelCheckerUtils Library.
*
* The ULTIMATE ModelCheckerUtils Library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The ULTIMATE ModelCheckerUtils Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the ULTIMATE ModelCheckerUtils Library. If not, see .
*
* Additional permission under GNU GPL version 3 section 7:
* If you modify the ULTIMATE ModelCheckerUtils Library, or any covered work, by linking
* or combining it with Eclipse RCP (or a modified version of Eclipse RCP),
* containing parts covered by the terms of the Eclipse Public License, the
* licensors of the ULTIMATE ModelCheckerUtils Library grant you additional permission
* to convey the resulting work.
*/
package de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import de.uni_freiburg.informatik.ultimate.core.model.models.ILocation;
import de.uni_freiburg.informatik.ultimate.core.model.models.ModelUtils;
import de.uni_freiburg.informatik.ultimate.core.model.services.IUltimateServiceProvider;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.boogie.Expression2Term.MultiTermResult;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IForkActionThreadCurrent.ForkSmtArguments;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfg;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgForkTransitionThreadCurrent;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IIcfgJoinTransitionThreadCurrent;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IJoinActionThreadCurrent.JoinSmtArguments;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgCallTransition;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgEdge;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgEdgeFactory;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgEdgeIterator;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgForkThreadCurrentTransition;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgInternalTransition;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgJoinThreadCurrentTransition;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.structure.IcfgLocation;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.transformations.BlockEncodingBacktranslator;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.transitions.TransFormulaBuilder;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.transitions.UnmodifiableTransFormula;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.ILocalProgramVar;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramNonOldVar;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.IProgramVar;
import de.uni_freiburg.informatik.ultimate.lib.modelcheckerutils.cfg.variables.ProgramVarUtils;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.ManagedScript;
import de.uni_freiburg.informatik.ultimate.lib.smtlibutils.Substitution;
import de.uni_freiburg.informatik.ultimate.logic.Term;
import de.uni_freiburg.informatik.ultimate.logic.TermVariable;
import de.uni_freiburg.informatik.ultimate.util.datastructures.BidirectionalMap;
import de.uni_freiburg.informatik.ultimate.util.datastructures.DataStructureUtils;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.HashRelation;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair;
/**
* Modifies a given {@link IIcfg} by adding copies of existing procedures.
*
* Copies are constructed according to the {@link List} argument "instances". For each instance with the name "foo" and
* the template "bar" we construct a copy of the procedure "foo" such that the identifier of the copy is "bar".
*
*
* A {@link ProcedureMultiplier} copies also all local variables accordingly and replaces the {@link CfgSmtToolkit}.
*
*
* @author Matthias Heizmann (heizmann@informatik.uni-freiburg.de)
* @author Frank Schüssele (schuessf@informatik.uni-freiburg.de)
* @author Dominik Klumpp (klumpp@informatik.uni-freiburg.de)
*/
public class ProcedureMultiplier {
private final BasicIcfg mIcfg;
private final HashRelation mCopyDirectives;
private final ManagedScript mMgdScript;
private final Set mNewProcedures;
private final DefaultIcfgSymbolTable mNewSymbolTable;
// Map from name of procedure duplicate and local variable of original procedure to local variable of duplicate.
private final Map> mLocalVariableMapping;
// Map from term variable for local variable copy to term variable for original local variable.
private final Map mVariableBacktranslationMapping = new HashMap<>();
// Map from pair of original location and procedure copy name to corresponding location in the procedure copy.
private final BidirectionalMap, IcfgLocation> mLocationMap = new BidirectionalMap<>();
// Map from pair of original edge and procedure copy name to corresponding edge in the procedure copy.
private final BidirectionalMap, IcfgEdge> mEdgeMap = new BidirectionalMap<>();
private final List mForkCurrentThreads = new ArrayList<>();
private final List mJoinCurrentThreads = new ArrayList<>();
public ProcedureMultiplier(final IUltimateServiceProvider services, final BasicIcfg icfg,
final List instances) {
this(services, icfg, generateCopyDirectives(instances));
}
public ProcedureMultiplier(final IUltimateServiceProvider services, final BasicIcfg icfg,
final HashRelation copyDirectives) {
mIcfg = icfg;
mCopyDirectives = copyDirectives;
final var originalCsToolkit = icfg.getCfgSmtToolkit();
mNewProcedures = DataStructureUtils.union(originalCsToolkit.getProcedures(), copyDirectives.projectToRange());
mNewSymbolTable = new DefaultIcfgSymbolTable(originalCsToolkit.getSymbolTable(), mNewProcedures);
mMgdScript = icfg.getCfgSmtToolkit().getManagedScript();
// Add variables
mLocalVariableMapping = duplicateVariables();
// Add locations and edges
for (final String proc : copyDirectives.getDomain()) {
duplicateProcedureEdges(proc);
}
}
// legacy API used in IcfgPetrifier
public static void duplicateProcedures(final IUltimateServiceProvider services, final BasicIcfg icfg,
final List instances, final BlockEncodingBacktranslator backtranslator,
final Map, List> threadInstanceMap,
final List> forkCurrentThreads,
final List> joinCurrentThreads) {
final var multiplier = new ProcedureMultiplier(services, icfg, instances);
forkCurrentThreads.addAll(multiplier.mForkCurrentThreads);
joinCurrentThreads.addAll(multiplier.mJoinCurrentThreads);
multiplier.updateThreadInstanceMap(threadInstanceMap);
multiplier.updateBacktranslator(backtranslator);
}
// -----------------------------------------------------------------------------------
// Getters and helpers for translating back and forth between original and duplicates.
// -----------------------------------------------------------------------------------
public Map getLocalVariableMapping(final String procedure) {
return Collections.unmodifiableMap(mLocalVariableMapping.get(procedure));
}
public IcfgLocation getOriginalLocation(final IcfgLocation duplicatedLocation) {
final var pair = mLocationMap.inverse().get(duplicatedLocation);
if (pair != null) {
return pair.getFirst();
}
return null;
}
public IcfgLocation getDuplicatedLocation(final IcfgLocation originalLocation, final String procedureCopy) {
return mLocationMap.get(new Pair<>(originalLocation, procedureCopy));
}
public IcfgEdge getOriginalEdge(final IcfgEdge duplicatedEdge) {
final var pair = mEdgeMap.inverse().get(duplicatedEdge);
if (pair != null) {
return pair.getFirst();
}
return null;
}
public IcfgEdge getDuplicatedEdge(final IcfgEdge originalEdge, final String procedureCopy) {
return mEdgeMap.get(new Pair<>(originalEdge, procedureCopy));
}
/**
* Updates a given backtranslator so that it can backtranslate edges created by the duplication and terms involving
* local variables of the procedure copies.
*
* @param backtranslator
* The backtranslator that shall be updated
*/
public void updateBacktranslator(final BlockEncodingBacktranslator backtranslator) {
backtranslator.setTermTranslator(x -> Substitution.apply(mMgdScript, mVariableBacktranslationMapping, x));
for (final var entry : mLocationMap.entrySet()) {
backtranslator.mapLocations(entry.getValue(), entry.getKey().getFirst());
}
for (final var entry : mEdgeMap.entrySet()) {
backtranslator.mapEdges(entry.getValue(), entry.getKey().getFirst());
}
}
public void updateThreadInstanceMap(
final Map, List> threadInstanceMap) {
for (final var newForkEdge : mForkCurrentThreads) {
final var oldForkEdge =
(IIcfgForkTransitionThreadCurrent>) mEdgeMap.inverse().get(newForkEdge).getFirst();
final List threadInstances = threadInstanceMap.get(oldForkEdge);
assert threadInstances != null;
threadInstanceMap.put(newForkEdge, threadInstances);
}
}
// -------------------------------------
// Actual procedure multiplication code.
// -------------------------------------
private void duplicateProcedureEdges(final String proc) {
final IcfgLocation procEntry = mIcfg.getProcedureEntryNodes().get(proc);
final List edges =
new IcfgEdgeIterator(procEntry.getOutgoingEdges()).asStream().collect(Collectors.toList());
for (final String copyIdentifier : mCopyDirectives.getImage(proc)) {
final var localVariableMap = getLocalVariableMapping(copyIdentifier);
for (final IcfgEdge edge : edges) {
final IcfgLocation source = getOrConstructLocationCopy(edge.getSource(), copyIdentifier);
final IcfgLocation target = getOrConstructLocationCopy(edge.getTarget(), copyIdentifier);
final IcfgEdge newEdge = constructEdgeCopy(edge, source, target, localVariableMap);
final var previousEdge = mEdgeMap.put(new Pair<>(edge, copyIdentifier), newEdge);
assert previousEdge == null;
ModelUtils.copyAnnotations(edge, newEdge);
source.addOutgoing(newEdge);
target.addIncoming(newEdge);
}
}
}
private Map> duplicateVariables() {
final Map> oldVar2newVar = new HashMap<>();
final CfgSmtToolkit cfgSmtToolkit = mIcfg.getCfgSmtToolkit();
final var originalSymbolTable = cfgSmtToolkit.getSymbolTable();
final Object lockOwner = this;
final HashRelation proc2globals =
new HashRelation<>(cfgSmtToolkit.getModifiableGlobalsTable().getProcToGlobals());
final Map> inParams = new HashMap<>(cfgSmtToolkit.getInParams());
final Map> outParams = new HashMap<>(cfgSmtToolkit.getOutParams());
mMgdScript.lock(lockOwner);
for (final String proc : mCopyDirectives.getDomain()) {
assert cfgSmtToolkit.getProcedures().contains(proc) : "procedure " + proc + " missing";
for (final String copyIdentifier : mCopyDirectives.getImage(proc)) {
mIcfg.addProcedure(copyIdentifier);
final Map procOldVar2NewVar = new HashMap<>();
final List inParamsOfCopy = new ArrayList<>();
for (final ILocalProgramVar inParam : inParams.get(proc)) {
final ILocalProgramVar inParamCopy =
constructVariableCopy(inParam, copyIdentifier, procOldVar2NewVar);
inParamsOfCopy.add(inParamCopy);
}
inParams.put(copyIdentifier, inParamsOfCopy);
final List outParamsOfCopy = new ArrayList<>();
for (final ILocalProgramVar outParam : outParams.get(proc)) {
final ILocalProgramVar outParamCopy =
constructVariableCopy(outParam, copyIdentifier, procOldVar2NewVar);
outParamsOfCopy.add(outParamCopy);
}
outParams.put(copyIdentifier, outParamsOfCopy);
for (final ILocalProgramVar localVar : originalSymbolTable.getLocals(proc)) {
if (!procOldVar2NewVar.containsKey(localVar)) {
constructVariableCopy(localVar, copyIdentifier, procOldVar2NewVar);
}
}
oldVar2newVar.put(copyIdentifier, procOldVar2NewVar);
proc2globals.getImage(proc).forEach(g -> proc2globals.addPair(copyIdentifier, g));
}
}
mMgdScript.unlock(lockOwner);
final SmtFunctionsAndAxioms smtSymbols = cfgSmtToolkit.getSmtFunctionsAndAxioms();
final CfgSmtToolkit newCfgSmtToolkit = new CfgSmtToolkit(new ModifiableGlobalsTable(proc2globals), mMgdScript,
mNewSymbolTable, mNewProcedures, inParams, outParams, cfgSmtToolkit.getIcfgEdgeFactory(),
cfgSmtToolkit.getConcurrencyInformation(), smtSymbols);
mIcfg.setCfgSmtToolkit(newCfgSmtToolkit);
return oldVar2newVar;
}
private IcfgEdge constructEdgeCopy(final IcfgEdge edge, final IcfgLocation source, final IcfgLocation target,
final Map varReplacement) {
final UnmodifiableTransFormula transFormula =
TransFormulaBuilder.constructCopy(mMgdScript, edge.getTransformula(), varReplacement);
final IcfgEdgeFactory icfgEdgeFactory = mIcfg.getCfgSmtToolkit().getIcfgEdgeFactory();
if (edge instanceof IcfgInternalTransition) {
final UnmodifiableTransFormula transFormulaWithBE = TransFormulaBuilder.constructCopy(mMgdScript,
((IcfgInternalTransition) edge).getTransitionFormulaWithBranchEncoders(), varReplacement);
return icfgEdgeFactory.createInternalTransition(source, target, null, transFormula, transFormulaWithBE);
}
if (edge instanceof IcfgForkThreadCurrentTransition) {
final IcfgForkThreadCurrentTransition oldForkEdge = (IcfgForkThreadCurrentTransition) edge;
final ForkSmtArguments newForkSmtArguments =
copyForkSmtArguments(oldForkEdge.getForkSmtArguments(), varReplacement);
final IcfgForkThreadCurrentTransition newForkEdge = icfgEdgeFactory.createForkThreadCurrentTransition(
source, target, null, transFormula, newForkSmtArguments, oldForkEdge.getNameOfForkedProcedure());
mForkCurrentThreads.add(newForkEdge);
return newForkEdge;
}
if (edge instanceof IcfgJoinThreadCurrentTransition) {
final JoinSmtArguments newForkSmtArguments = copyJoinSmtArguments(
((IcfgJoinThreadCurrentTransition) edge).getJoinSmtArguments(), varReplacement);
final IcfgJoinThreadCurrentTransition newJoinEdge = icfgEdgeFactory
.createJoinThreadCurrentTransition(source, target, null, transFormula, newForkSmtArguments);
mJoinCurrentThreads.add(newJoinEdge);
return newJoinEdge;
}
if (edge instanceof IcfgCallTransition) {
throw new UnsupportedOperationException(String.format(
"%s does not support %s. Calls and returns should habe been removed by inlining. "
+ "(Did the inlining fail because this program is recursive?)",
ProcedureMultiplier.class.getSimpleName(), edge.getClass().getSimpleName()));
}
throw new UnsupportedOperationException(
ProcedureMultiplier.class.getSimpleName() + " does not support " + edge.getClass().getSimpleName());
}
private IcfgLocation getOrConstructLocationCopy(final IcfgLocation originalLocation, final String newProc) {
return mLocationMap.computeIfAbsent(new Pair<>(originalLocation, newProc), this::constructLocationCopy);
}
private IcfgLocation constructLocationCopy(final Pair originalLocAndNewProc) {
final var originalLocation = originalLocAndNewProc.getFirst();
final String originalProc = originalLocation.getProcedure();
final String newProc = originalLocAndNewProc.getSecond();
final IcfgLocation newLoc = new IcfgLocation(originalLocation.getDebugIdentifier(), newProc);
ModelUtils.copyAnnotations(originalLocation, newLoc);
final boolean isInitial = mIcfg.getInitialNodes().contains(originalLocation);
final boolean isError = mIcfg.getProcedureErrorNodes().get(originalProc).contains(originalLocation);
final boolean isProcEntry = mIcfg.getProcedureEntryNodes().get(originalProc).equals(originalLocation);
final boolean isProcExit = mIcfg.getProcedureExitNodes().get(originalProc).equals(originalLocation);
final boolean isLoopLocation = mIcfg.getLoopLocations().contains(originalLocation);
mIcfg.addLocation(newLoc, isInitial, isError, isProcEntry, isProcExit, isLoopLocation);
return newLoc;
}
private ILocalProgramVar constructVariableCopy(final ILocalProgramVar localVar, final String procedureCopy,
final Map procOldVar2NewVar) {
final ILocalProgramVar localVarCopy = ProgramVarUtils.constructLocalProgramVar(localVar.getIdentifier(),
procedureCopy, localVar.getSort(), mMgdScript, this);
procOldVar2NewVar.put(localVar, localVarCopy);
mVariableBacktranslationMapping.put(localVarCopy.getTermVariable(), localVar.getTermVariable());
mNewSymbolTable.add(localVarCopy);
return localVarCopy;
}
private ForkSmtArguments copyForkSmtArguments(final ForkSmtArguments forkSmtArguments,
final Map map) {
final Map defaultVariableMapping = constructDefaultVariableMapping(map);
final MultiTermResult newThreadIdArguments =
copyMultiTermResult(forkSmtArguments.getThreadIdArguments(), defaultVariableMapping);
final MultiTermResult newProcedureArguments =
copyMultiTermResult(forkSmtArguments.getProcedureArguments(), defaultVariableMapping);
return new ForkSmtArguments(newThreadIdArguments, newProcedureArguments);
}
private JoinSmtArguments copyJoinSmtArguments(final JoinSmtArguments joinSmtArguments,
final Map map) {
final Map defaultVariableMapping = constructDefaultVariableMapping(map);
final MultiTermResult newThreadIdArguments =
copyMultiTermResult(joinSmtArguments.getThreadIdArguments(), defaultVariableMapping);
final List newAssignmentLhs = joinSmtArguments.getAssignmentLhs().stream()
.map(x -> x.isGlobal() ? x : map.get(x)).collect(Collectors.toList());
return new JoinSmtArguments(newThreadIdArguments, newAssignmentLhs);
}
private MultiTermResult copyMultiTermResult(final MultiTermResult oldProcedureArguments,
final Map defaultVariableMapping) {
final UnaryOperator subst = (x -> Substitution.apply(mMgdScript, defaultVariableMapping, x));
final Term[] terms = Arrays.stream(oldProcedureArguments.getTerms()).map(subst).toArray(Term[]::new);
final Collection auxiliaryVars = oldProcedureArguments.getAuxiliaryVars();
final Map overapproximations = oldProcedureArguments.getOverappoximations();
final MultiTermResult copy = new MultiTermResult(overapproximations, auxiliaryVars, terms);
return copy;
}
private static Map constructDefaultVariableMapping(final Map map) {
return map.entrySet().stream()
.collect(Collectors.toMap(x -> x.getKey().getTermVariable(), x -> x.getValue().getTermVariable()));
}
private static HashRelation
generateCopyDirectives(final Collection threadInstances) {
final HashRelation result = new HashRelation<>();
for (final ThreadInstance ti : threadInstances) {
result.addPair(ti.getThreadTemplateName(), ti.getThreadInstanceName());
}
return result;
}
}