blob: 837f0af9426d35a544b37827fa9a054d941ff3a3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 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;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.QVTm2QVTs;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.RegionUtil;
import org.eclipse.qvtd.pivot.qvtschedule.DatumConnection;
import org.eclipse.qvtd.pivot.qvtschedule.NodeConnection;
import org.eclipse.qvtd.pivot.qvtschedule.Region;
import org.eclipse.qvtd.pivot.qvtschedule.ScheduledRegion;
import com.google.common.collect.Iterables;
/**
* CallTreeBuilder simulates execution of the indexed regions resulting in a call tree that ensures that each regions
* is executed after its predecessors, nested to re-use as much prior context from the call stack.
*/
public class CallTreeBuilder
{
private final @NonNull ScheduleCache scheduleCache;
/**
* Working state: the common region for all uses of each connection.
*/
private final @NonNull Map<@NonNull NodeConnection, @NonNull Region> connection2commonRegion = new HashMap<@NonNull NodeConnection, @NonNull Region>();
public CallTreeBuilder(@NonNull ScheduleCache scheduleCache) {
this.scheduleCache = scheduleCache;
}
public void buildTree(@NonNull ScheduledRegion rootScheduledRegion, @NonNull List<@NonNull Region> orderedRegions) {
Stack<@NonNull Region> callStack = new Stack<@NonNull Region>();
callStack.push(rootScheduledRegion);
for (@NonNull Region region: orderedRegions) {
updateCallStack(callStack, region);
}
installConnections();
}
protected @NonNull Region getCommonRegion(@NonNull Region firstRegion, @NonNull Region secondRegion) {
Region commonRegion = scheduleCache.getCommonRegion(firstRegion, secondRegion);
assert commonRegion != null;
return commonRegion;
}
protected @NonNull Region getMinimumDepthParentRegion(@NonNull Region childRegion) {
Region minimumDepthParentRegion = scheduleCache.getMinimumDepthParentRegion(childRegion);
assert minimumDepthParentRegion != null;
return minimumDepthParentRegion;
}
/**
* Install all connections so that the connection variable is declared at the common region and passed through all the
* intermediate regions between the common region and the regions that use the connection variable as a source or target.
*/
protected void installConnections() {
List<@NonNull NodeConnection> connections = new ArrayList<>(connection2commonRegion.keySet());
Collections.sort(connections, new Comparator<@NonNull NodeConnection>() // impriive determinism
{
@Override
public int compare(@NonNull NodeConnection o1, @NonNull NodeConnection o2) {
List<@NonNull Integer> l1 = o1.getIndexes();
List<@NonNull Integer> l2 = o2.getIndexes();
int x1 = l1.size() > 0 ? l1.get(0) : -1;
int x2 = l2.size() > 0 ? l2.get(0) : -1;
return x1-x2;
}
});
for (@NonNull NodeConnection connection : connections) {
if (connection.isPassed()) {
Region commonRegion = connection2commonRegion.get(connection);
assert commonRegion != null;
List<@NonNull Region> intermediateRegions = new ArrayList<@NonNull Region>();
// boolean isCyclic = scheduledRegion.isCyclicScheduledRegion();
// if (isCyclic) { // FIXME should not be necessary
// intermediateRegions.add(scheduledRegion);
// }
for (@NonNull Region sourceRegion : scheduleCache.getSourceRegions(connection)) {
if (sourceRegion != commonRegion) { //|| sourceRegion.isCyclicScheduledRegion()) {
Iterable<@NonNull Region> sourceRegions = Collections.singletonList(sourceRegion);
installConnectionsLocateIntermediates(intermediateRegions, sourceRegions, commonRegion);
}
}
for (@NonNull Region targetRegion : scheduleCache.getTargetRegions(connection)) {
if ((targetRegion != commonRegion) && connection.isPassed(targetRegion)) {
Iterable<@NonNull Region> targetRegions2 = /*targetRegion.isCyclicScheduledRegion() ? Collections.singletonList(targetRegion) :*/ targetRegion.getCallableParents();
installConnectionsLocateIntermediates(intermediateRegions, targetRegions2, commonRegion);
}
}
connection.setCommonRegion(commonRegion, intermediateRegions);
// Scheduler.REGION_LOCALITY.println(connection + " => " + commonRegion + " " + intermediateRegions);
}
}
for (@NonNull NodeConnection connection : connections) {
if (connection.isPassed()) {
Region commonRegion = connection.getCommonRegion();
assert commonRegion != null;
List<@NonNull Region> intermediateRegions = connection.getIntermediateRegions();
for (@NonNull Region intermediateRegion : intermediateRegions) {
Region checkCommonRegion = commonRegion.getLoopingConnections().size() > 0 ? RegionUtil.getInvokingRegion(commonRegion) : commonRegion;
assert commonRegion.getLoopingConnections().size() > 0
? Iterables.contains(commonRegion.getCallableParents(), getCommonRegion(commonRegion, intermediateRegion))
: getCommonRegion(commonRegion, intermediateRegion) == checkCommonRegion;
}
}
}
}
/**
* Recursively locate all the regions that are traversed as a call sub-tree rooted at commonRegion invokes each of callableParents.
*/
protected void installConnectionsLocateIntermediates(@NonNull List<@NonNull Region> intermediateRegions, @NonNull Iterable<@NonNull Region> callableParents, @NonNull Region commonRegion) {
for (@NonNull Region callableParent : callableParents) {
if (callableParent != commonRegion) {
if (!intermediateRegions.contains(callableParent)) {
intermediateRegions.add(callableParent);
installConnectionsLocateIntermediates(intermediateRegions, callableParent.getCallableParents(), commonRegion);
}
}
}
}
/**
* Update the callStack so that region is at the top. Update connection2commonRegion so that all
* region's incomingConnections are accessible from a commonRegion on the callStack.
*/
protected void updateCallStack(@NonNull Stack<@NonNull Region> callStack, @NonNull Region region) {
QVTm2QVTs.REGION_STACK.println(region.getSymbolName() + " => " + callStack);
//
// Pop stack to commonRegion
//
Region topOfStack = callStack.peek();
assert topOfStack != null;
@NonNull Region commonRegion = getCommonRegion(topOfStack, region);
//
// Must identify the call point at which all source values are available.
//
// The 'easy' safe inefficient case is the caller of the common region of all sources.
//
// FIXME: The optimized case for a single-headed region ensures that for all candidate sources for the head,
// the to-one region from the head's source covers all the required producer-consumer dependencies.
//
// Obsolete special case: If the caller is a recursion, ensure the the caller's caller is on the stack.
//
/* for (@NonNull DatumConnection incomingConnection1 : scheduleCache.getIncomingConnections(region)) { // FIXME passed
for (@NonNull Region sourceRegion1 : scheduleCache.getSourceRegions(incomingConnection1)) {
if (sourceRegion1.getLoopingConnections().size() > 0) {
for (@NonNull DatumConnection incomingConnection2 : scheduleCache.getIncomingConnections(sourceRegion1)) { // FIXME passed
for (@NonNull Region sourceRegion2 : scheduleCache.getSourceRegions(incomingConnection2)) {
commonRegion = getCommonRegion(commonRegion, sourceRegion2);
}
}
}
}
} */
for (@NonNull DatumConnection<?> incomingConnection1 : scheduleCache.getIncomingConnections(region)) {
for (@NonNull Region sourceRegion1 : scheduleCache.getSourceRegions(incomingConnection1)) {
// if (sourceRegion1.getLoopingConnections().size() > 0) {
for (@NonNull DatumConnection<?> incomingConnection2 : scheduleCache.getIncomingConnections(sourceRegion1)) {
for (@NonNull Region sourceRegion2 : scheduleCache.getSourceRegions(incomingConnection2)) {
commonRegion = getCommonRegion(commonRegion, sourceRegion2);
}
}
// }
}
}
while (!callStack.contains(commonRegion)) {
commonRegion = getMinimumDepthParentRegion(commonRegion);
assert commonRegion != null;
}
while ((topOfStack != commonRegion) && (topOfStack != region)) {
callStack.pop();
Region topOfStack2 = callStack.peek();
assert topOfStack2 != null;
topOfStack = topOfStack2;
}
//
// Ensure that passed connections are hosted by a mutually common region.
//
// Iterable<@NonNull DatumConnection> incomingConnections = getIncomingConnections(region);
// assert incomingConnections != null;
for (@NonNull DatumConnection<?> incomingConnection : scheduleCache.getIncomingConnections(region)) {
if (incomingConnection.isPassed(region)) {
commonRegion = updateConnectionLocality((@NonNull NodeConnection) incomingConnection, commonRegion);
}
}
// Iterable<@NonNull DatumConnection> outgoingConnections = getOutgoingConnections(region);
// assert outgoingConnections != null;
// for (@NonNull DatumConnection outgoingConnection : outgoingConnections) {
// if (outgoingConnection.isOutput()) {
// commonRegion = updateConnectionLocality((@NonNull NodeConnection) outgoingConnection, commonRegion);
// }
// }
//
// Push stack to region (without re-traversing predecessors)
//
if (topOfStack != region) {
topOfStack.addCallToChild(region);
callStack.push(region);
topOfStack = region;
}
assert topOfStack == callStack.peek();
assert topOfStack == region;
}
/**
* Update the connection-specific common-region of connection to be a common-region of commonStackRegion
* and all source and all target regions of connection.
*/
protected @NonNull Region updateConnectionLocality(@NonNull NodeConnection connection, @NonNull Region commonStackRegion) {
assert connection.isPassed();
Region oldCommonRegion = connection2commonRegion.get(connection);
@NonNull Region commonRegion;
if (oldCommonRegion == null) {
commonRegion = commonStackRegion;
for (@NonNull Region sourceRegion : scheduleCache.getSourceRegions(connection)) {
commonRegion = getCommonRegion(commonRegion, sourceRegion);
}
}
else {
commonRegion = getCommonRegion(oldCommonRegion, commonStackRegion);
}
if (oldCommonRegion != commonRegion) {
connection2commonRegion.put(connection, commonRegion);
}
return commonRegion;
}
}