blob: d183fe4a637d7764486348b5b7064f39f30de67f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Willink Transformations and others.
* 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:
* E.D.Willink - Initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.compiler.internal.qvts2qvts.merger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.Property;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.ContentsAnalysis;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.QVTm2QVTs;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.RegionUtil;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.ScheduleManager;
import org.eclipse.qvtd.compiler.internal.qvts2qvts.ClassDatumAnalysis;
import org.eclipse.qvtd.pivot.qvtschedule.MappingRegion;
import org.eclipse.qvtd.pivot.qvtschedule.NavigableEdge;
import org.eclipse.qvtd.pivot.qvtschedule.Node;
import org.eclipse.qvtd.pivot.qvtschedule.NodeConnection;
import org.eclipse.qvtd.pivot.qvtschedule.Region;
import org.eclipse.qvtd.pivot.qvtschedule.ScheduledRegion;
import org.eclipse.qvtd.pivot.qvtschedule.impl.NamedMappingRegionImpl;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* LateConsumerMerger replaces one list of MappingRegions by another in which each set of regions that
* share a consumption and can be merged exploiting knowledge of the schedule is replaced by an
* equivalent merged region.
*
* Preconditions:
*
* Late consumer merge must occur at all possible merge sites, or none. That is, a merged region that consumes
* some node and surrounding predicates at ts head must merge with all other regions that consume that
* head node with non-conflicting predicates.
*
* Every predicated edge/node of a late merged region must be shared with or satisfied by the other region.
*/
public class LateConsumerMerger extends AbstractMerger
{
public static class LateMergedMappingRegion extends NamedMappingRegionImpl
{
public LateMergedMappingRegion(@NonNull ScheduleManager scheduleManager, @NonNull String name) {
setFixmeScheduleModel(scheduleManager.getScheduleModel());
setName(name);
setSymbolNameSuffix("_lc");
}
}
protected static class LateRegionMerger extends RegionMerger
{
protected LateRegionMerger(@NonNull MappingRegion primaryRegion) {
super(primaryRegion);
}
@Override
protected @NonNull MappingRegion createNewRegion(@NonNull String newName) {
return new LateMergedMappingRegion(RegionUtil.getScheduleManager(primaryRegion), newName);
}
public void install(@NonNull ContentsAnalysis contentsAnalysis, @NonNull MappingRegion mergedRegion) {
ScheduledRegion invokingRegion = RegionUtil.getOwningScheduledRegion(primaryRegion);
List<@NonNull Region> callableParents = Lists.newArrayList(primaryRegion.getCallableParents());
contentsAnalysis.removeRegion(primaryRegion);
for (@NonNull Region callableParent : callableParents) {
callableParent.replaceCallToChild(primaryRegion, mergedRegion);
}
primaryRegion.setOwningScheduledRegion(invokingRegion);
for (@NonNull Region secondaryRegion : secondaryRegions) {
contentsAnalysis.removeRegion(secondaryRegion);
assert invokingRegion == secondaryRegion.getOwningScheduledRegion();
for (@NonNull Region callableParent : callableParents) {
callableParent.removeCallToChild(secondaryRegion);
}
secondaryRegion.setOwningScheduledRegion(invokingRegion);
}
contentsAnalysis.addRegion(mergedRegion);
mergedRegion.setOwningScheduledRegion(invokingRegion);
for (@NonNull Node oldHeadNode : RegionUtil.getHeadNodes(primaryRegion)) {
NodeConnection incomingConnection = oldHeadNode.getIncomingConnection();
if (incomingConnection != null) {
Node newHeadNode = getNodeMerger(oldHeadNode).getNewNode();
incomingConnection.addPassedTargetNode(newHeadNode);
incomingConnection.removeTargetRegion(primaryRegion);
for (@NonNull Region secondaryRegion : secondaryRegions) {
incomingConnection.removeTargetRegion(secondaryRegion);
}
}
}
}
}
private class LateStrategy extends Correlator.AbstractCorrelationStrategy
{
@Override
public boolean navigableEdgesMatch(@NonNull NavigableEdge secondaryEdge, @Nullable NavigableEdge primaryEdge) { // assert same property, skip secondary properties
if (secondaryEdge.isSecondary()) { // Ignore opposites - checked by theit forward edge
return true;
}
Property property = secondaryEdge.getProperty();
if (primaryEdge == null) {
if (/*!secondaryEdge.isRequired() ||*/ !secondaryEdge.isUnconditional()) {
return true;
}
if (secondaryEdge.isConstant()) {
return true;
}
if (secondaryEdge.isLoaded()) {
return true;
}
if (secondaryEdge.isNew()) {
return true;
}
if (secondaryEdge.isOld()) {
if (!secondaryEdge.getEdgeTarget().isRequired()) {
return true; // If target is optional, it's absence cannot cause a failure
}
if (!property.isIsRequired()) {
return true; // If property is optional, it's absence cannot cause a failure
}
ClassDatumAnalysis classDatumAnalysis = RegionUtil.getClassDatumAnalysis(secondaryEdge.getEdgeTarget());
Iterable<@NonNull NavigableEdge> realizedEdges = getContentsAnalysis().getNewEdges(secondaryEdge, classDatumAnalysis);
if (realizedEdges != null) {
int firstIndex = secondaryEdge.getOwningRegion().getFirstIndex();
for (@NonNull NavigableEdge realizedEdge : realizedEdges) {
Region region = realizedEdge.getOwningRegion();
int lastIndex = region.getLastIndex();
if (lastIndex >= firstIndex) {
if (debugFailures) {
FAILURE.println("Not ready : " + realizedEdge);
}
return false; // Missing edge must be unconditional somewhere
}
}
return true; // All realizations are earlier
}
// if (property.isIsRequired()) {
// return true; // Property is required therefore it must be assigned elsewhere (and earlier)
// }
// FIXME If there is a mandatory assignment to the optional property => true
toString();
}
if (debugFailures) {
FAILURE.println("Missing predicated match for : " + secondaryEdge);
}
return false; // Missing edge must be unconditional somewhere
}
else {
assert primaryEdge.getProperty() == property;
if (/*!primaryEdge.isRequired() ||*/ !primaryEdge.isUnconditional()) {
return true;
}
if (secondaryEdge.isConstant() || primaryEdge.isConstant()) {
if (secondaryEdge.isConstant() != primaryEdge.isConstant()) {
System.out.println("Inconsistent constant: " + secondaryEdge + ", " + primaryEdge);
}
return true;
}
if (secondaryEdge.isLoaded() || primaryEdge.isLoaded()) {
if (secondaryEdge.isLoaded() != primaryEdge.isLoaded()) {
System.out.println("Inconsistent loaded: " + secondaryEdge + ", " + primaryEdge);
}
return true;
}
if (secondaryEdge.isNew() || primaryEdge.isNew()) {
if (secondaryEdge.isNew() == primaryEdge.isNew()) { // == should only be one REALIZED
System.out.println("Inconsistent new: " + secondaryEdge + ", " + primaryEdge);
}
return true;
}
if (secondaryEdge.isOld() || primaryEdge.isOld()) {
if (secondaryEdge.isOld() != primaryEdge.isOld()) {
System.out.println("Inconsistent old: " + secondaryEdge + ", " + primaryEdge);
}
return true;
}
if (debugFailures) {
FAILURE.println("Inconsistent edges : " + secondaryEdge + ", " + primaryEdge);
}
return false; //super.navigableEdgesMatch(secondaryEdge, primaryEdge);
}
}
@Override
public boolean navigableNodesMatch(@NonNull Node secondaryNode, @Nullable Node primaryNode) {
if (!secondaryNode.isRequired() || !secondaryNode.isUnconditional()) {
return true;
}
if (primaryNode == null) {
return true; // Missing node must be created somewhere else (?? duplicates missing edge logic)
}
else {
if (!primaryNode.isRequired() || !primaryNode.isUnconditional()) {
return true;
}
if (secondaryNode.isConstant() || primaryNode.isConstant()) {
if (secondaryNode.isConstant() != primaryNode.isConstant()) {
System.out.println("Inconsistent constant: " + secondaryNode + ", " + primaryNode);
}
return true;
}
if (secondaryNode.isLoaded() || primaryNode.isLoaded()) {
if (secondaryNode.isLoaded() != primaryNode.isLoaded()) {
System.out.println("Inconsistent loaded: " + secondaryNode + ", " + primaryNode);
}
return true;
}
if (secondaryNode.isNew() || primaryNode.isNew()) {
if (secondaryNode.isNew() == primaryNode.isNew()) { // == should only be one REALIZED
System.out.println("Inconsistent new: " + secondaryNode + ", " + primaryNode);
}
return true;
}
if (secondaryNode.isOld() || primaryNode.isOld()) {
if (secondaryNode.isOld() != primaryNode.isOld()) {
System.out.println("Inconsistent old: " + secondaryNode + ", " + primaryNode);
}
return true;
}
if (debugFailures) {
FAILURE.println("Inconsistent nodes : " + secondaryNode + ", " + primaryNode);
}
}
return false;//super.navigableNodesMatch(secondaryNode, primaryNode);
}
}
/**
* Replace those inputRegions that may be merged by merged regions.
*
* inputRegions should be indexed to encourage consecutive indexes for regions sharing an input connection.
*/
public static @NonNull Map<@NonNull Region, @NonNull List<@NonNull Region>> merge(@NonNull ScheduledRegion scheduledRegion) {
LateConsumerMerger lateMerger = new LateConsumerMerger(scheduledRegion);
lateMerger.merge();
if (QVTm2QVTs.DEBUG_GRAPHS.isActive()) {
RegionUtil.getScheduleManager(scheduledRegion).writeDebugGraphs(scheduledRegion, "8-late", true, true, false);
}
return lateMerger.getMerges();
}
protected final @NonNull ScheduledRegion scheduledRegion;
protected final @NonNull List<@NonNull Region> allRegions = new ArrayList<>();
private /*@LazyNonNull*/ ContentsAnalysis contentsAnalysis;
private final @NonNull Map<@NonNull Region, @NonNull List<@NonNull Region>> newRegion2oldRegions = new HashMap<>();
protected final @NonNull LateStrategy LateStrategy_INSTANCE = new LateStrategy();
public LateConsumerMerger(@NonNull ScheduledRegion scheduledRegion) {
this.scheduledRegion = scheduledRegion;
gatherRegions(scheduledRegion);
}
private void gatherRegions(@NonNull Region parentRegion) {
for (@NonNull Region childRegion : parentRegion.getCallableChildren()) {
allRegions.add(childRegion);
gatherRegions(childRegion);
}
return;
}
protected @NonNull ContentsAnalysis getContentsAnalysis() {
ContentsAnalysis contentsAnalysis2 = contentsAnalysis;
if (contentsAnalysis2 == null) {
contentsAnalysis2 = contentsAnalysis = new ContentsAnalysis(RegionUtil.getScheduleManager(scheduledRegion));
for (@NonNull Region region : allRegions) {
contentsAnalysis2.addRegion(region);
}
}
return contentsAnalysis2;
}
public @NonNull Map<@NonNull Region, @NonNull List<@NonNull Region>> getMerges() {
return newRegion2oldRegions;
}
/* private @Nullable Iterable<@NonNull Region> getSharedConsumers(@NonNull Region primaryRegion) {
Set<@NonNull Region> sharedRegions = null;
List<@NonNull Node> headNodes = primaryRegion.getHeadNodes();
for (@NonNull Node headNode : headNodes) {
Iterable<@NonNull Node> oldNodes = getContentsAnalysis().getOldNodes(headNode.getClassDatumAnalysis());
if (oldNodes != null) {
for (@NonNull Node oldNode : oldNodes) {
Region region = oldNode.getRegion();
if (region != primaryRegion) {
if (sharedRegions == null) {
sharedRegions = new HashSet<>();
}
sharedRegions.add(region);
}
}
}
}
return sharedRegions;
} */
private void merge() {
mergeHierarchy(scheduledRegion);
return;
}
private void mergeHierarchy(@NonNull Region parentRegion) {
for (@NonNull Region childRegion : parentRegion.getCallableChildren()) {
mergeHierarchy(childRegion);
mergeRegion(childRegion);
}
return;
}
private void mergeRegion(@NonNull Region parentRegion) {
for (@NonNull NodeConnection nodeConnection : parentRegion.getRootConnections()) {
// System.out.println(nodeConnection + " " + nodeConnection.getIndexes());
Iterable<@NonNull List<@NonNull MappingRegion>> consecutiveRegionRuns = selectConsecutiveRegionRuns(nodeConnection);
for (@NonNull List<@NonNull MappingRegion> consecutiveRegionRun : consecutiveRegionRuns) {
StringBuilder s = new StringBuilder();
s.append(Iterables.size(consecutiveRegionRun));
for (@NonNull Region consecutiveRegion : consecutiveRegionRun) {
s.append(" " + consecutiveRegion);
}
// System.out.println(s.toString());
mergeRegions(consecutiveRegionRun);
}
}
return;
}
protected void mergeRegions(@NonNull List<@NonNull MappingRegion> consecutiveRegionRun) {
List<@NonNull MappingRegion> residualInputRegions = new ArrayList<>(consecutiveRegionRun);
while (residualInputRegions.size() >= 2) {
MappingRegion primaryRegion = residualInputRegions.remove(0);
if (LATE.isActive()) {
LATE.println("Correlating primary: " + primaryRegion + "@[" + primaryRegion.getIndexRangeText() + "]");
}
if (primaryRegion.getIntermediateConnections().size() > 0) { // FIXME this should be allowed
if (FAILURE.isActive()) {
FAILURE.println("Intermediate connections not yet supported");
}
return;
}
/* Iterable<@NonNull Region> sharedConsumers = getSharedConsumers(primaryRegion);
if (sharedConsumers != null) {
for (@NonNull Region sharedConsumer : sharedConsumers) {
if (!residualInputRegions.contains(sharedConsumer)) {
if (FAILURE.isActive()) {
FAILURE.println("Unshareable shared region: " + sharedConsumer + "@[" + sharedConsumer.getIndexRangeText() + "]");
}
return;
}
}
} */
LateRegionMerger regionMerger = null;
for (int i = 0; i < residualInputRegions.size(); i++) {
MappingRegion secondaryRegion = residualInputRegions.get(i);
if (LATE.isActive()) {
LATE.println("Correlating secondary: " + secondaryRegion + "@[" + secondaryRegion.getIndexRangeText() + "]");
}
if (secondaryRegion.getIntermediateConnections().size() > 0) { // FIXME this should be allowed
if (FAILURE.isActive()) {
FAILURE.println("Intermediate connections not yet supported");
}
}
else {
Correlator secondary2primary = Correlator.correlate(secondaryRegion, primaryRegion, LateStrategy_INSTANCE, null);
if (secondary2primary != null) {
boolean doMerge = false;
if (!isSharedHead(primaryRegion, secondaryRegion)) {
doMerge = true;
}
else {
if (LATE.isActive()) {
LATE.println("Correlating inverse");
}
if (Correlator.correlate(primaryRegion, secondaryRegion, LateStrategy_INSTANCE, secondary2primary.getNode2Node()) != null) {
doMerge = true;
}
}
if (doMerge) {
if (regionMerger == null) {
// residualInputRegions.remove(primaryRegion);
regionMerger = new LateRegionMerger(primaryRegion);
}
// residualInputRegions.remove(secondaryRegion);
regionMerger.addSecondaryRegion(secondaryRegion, secondary2primary.getNode2Node());
}
}
}
}
if (regionMerger != null) {
// ?? involves all shareableConsumers
regionMerger.prune();
MappingRegion mergedRegion = regionMerger.create();
regionMerger.check(mergedRegion);
regionMerger.install(getContentsAnalysis(), mergedRegion);
List<@NonNull Region> oldRegions = new ArrayList<>();
oldRegions.add(primaryRegion);
oldRegions.addAll(regionMerger.getSecondaryRegions());
newRegion2oldRegions.put(mergedRegion, oldRegions);
residualInputRegions.removeAll(oldRegions);
for (@NonNull Region oldRegion : oldRegions) {
for (int index : oldRegion.getIndexes()) {
mergedRegion.addIndex(index);
}
}
RegionUtil.getScheduleManager(mergedRegion).writeDebugGraphs(mergedRegion, null);
}
}
}
protected @NonNull Iterable<@NonNull List<@NonNull MappingRegion>> selectConsecutiveRegionRuns(@NonNull NodeConnection nodeConnection) {
Map<@NonNull Integer, @NonNull MappingRegion> index2region = new HashMap<>();
for (@NonNull Region targetRegion : nodeConnection.getTargetRegions()) {
if (targetRegion instanceof MappingRegion) { // FIXME ?? always a MappingRegion
List<@NonNull Integer> indexes = targetRegion.getIndexes();
index2region.put(indexes.get(indexes.size()-1), (@NonNull MappingRegion) targetRegion);
}
}
List<@NonNull Integer> orderedIndexes = new ArrayList<>(index2region.keySet());
Collections.sort(orderedIndexes);
List<@NonNull List<@NonNull MappingRegion>> consecutiveRegionRuns = new ArrayList<>();
{
List<@NonNull MappingRegion> consecutiveRegionRun = null;
for (@NonNull Integer index : orderedIndexes) {
MappingRegion childRegion = index2region.get(index);
assert childRegion != null;
if ((consecutiveRegionRun == null) || (consecutiveRegionRun.get(consecutiveRegionRun.size()-1).getLastIndex()+1 != index)) {
consecutiveRegionRun = new ArrayList<>();
consecutiveRegionRuns.add(consecutiveRegionRun);
}
consecutiveRegionRun.add(childRegion);
}
}
for (int i = consecutiveRegionRuns.size(); --i >= 0; ) { // Down count to allow removal
List<@NonNull MappingRegion> consecutiveRegionRun = consecutiveRegionRuns.get(i);
if (consecutiveRegionRun.size() <= 1) {
consecutiveRegionRuns.remove(i);
}
}
return consecutiveRegionRuns;
}
}