blob: 85877dadd1de8937c334e1f4e304e4d12d2cfcb2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2019 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - Initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.compiler.internal.qvts2qvts.partitioner;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.qvtd.compiler.internal.qvtm2qvts.QVTm2QVTs;
import org.eclipse.qvtd.compiler.internal.qvts2qvts.utilities.ReachabilityForest;
import org.eclipse.qvtd.pivot.qvtschedule.BasicPartition;
import org.eclipse.qvtd.pivot.qvtschedule.Edge;
import org.eclipse.qvtd.pivot.qvtschedule.Node;
import org.eclipse.qvtd.pivot.qvtschedule.Node.Utility;
import org.eclipse.qvtd.pivot.qvtschedule.Role;
import org.eclipse.qvtd.pivot.qvtschedule.SharedEdge;
import org.eclipse.qvtd.pivot.qvtschedule.utilities.QVTscheduleUtil;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
/**
* The LocalPredicate Partition identifies the nodes and edges required to determine whether
* the local match is satisfied. This creates the speculated trace with predicates solely on
* constant inputs, loaded inputs and acyclic predicated nodes.
*/
//
// FIXME if multiple heads are predicated, we cannot wait for all of them before speculating. Speculation
// may need to consider just loaded nodes.
//
public class LocalPredicatePartitionFactory extends AbstractSimplePartitionFactory
{
protected final boolean useActivators;
private final @NonNull Iterable<@NonNull Node> executionNodes;
private final @NonNull Iterable<@NonNull Node> realizedWhenNodes;
private final @Nullable Node dispatchNode;
private final @Nullable Iterable<@NonNull SharedEdge> sharedEdges;
private final @Nullable Iterable<@NonNull Node> sharedEdgeTargets;
public LocalPredicatePartitionFactory(@NonNull MappingPartitioner mappingPartitioner, boolean useActivators) {
super(mappingPartitioner);
this.executionNodes = mappingPartitioner.getExecutionNodes();
this.useActivators = useActivators;
this.realizedWhenNodes = mappingPartitioner.getRealizedWhenNodes();
this.dispatchNode = mappingPartitioner.basicGetDispatchNode();
List<@NonNull SharedEdge> sharedEdges = null;
List<@NonNull Node> sharedEdgeTargets = null;
for (@NonNull Edge edge : QVTscheduleUtil.getOwnedEdges(region)) {
if (edge.isShared()) {
if (sharedEdges == null) {
sharedEdges = new ArrayList<>();
}
sharedEdges.add((SharedEdge) edge);
if (sharedEdgeTargets == null) {
sharedEdgeTargets = new ArrayList<>();
}
Node targetNode = QVTscheduleUtil.getTargetNode(edge);
if (!sharedEdgeTargets.contains(targetNode)) { // In practice two shared edges is an error
sharedEdgeTargets.add(targetNode);
}
}
}
this.sharedEdges = sharedEdges;
this.sharedEdgeTargets = sharedEdgeTargets;
}
@Override
public @NonNull BasicPartitionAnalysis createPartitionAnalysis(@NonNull PartitionedTransformationAnalysis partitionedTransformationAnalysis) {
ReachabilityForest reachabilityForest = createReachabilityForest();
String name = computeName("local");
Iterable<@NonNull Node> headNodes = useActivators ? executionNodes : QVTscheduleUtil.getHeadNodes(mappingPartitioner.getRegion());
BasicPartition partition = createBasicPartition(name, headNodes);
int partitionNumber = region.getNextPartitionNumber();
BasicPartitionAnalysis basicPartitionAnalysis = new BasicPartitionAnalysis(partitionedTransformationAnalysis, partition, reachabilityForest, "«local»", "_p" + partitionNumber);
initializePartition(basicPartitionAnalysis);
if (QVTm2QVTs.DEBUG_GRAPHS.isActive()) {
scheduleManager.writeDebugGraphs(partition, null);
}
return basicPartitionAnalysis;
}
/**
* Add all old nodes, including node, that have no cyclic dependency and are reachable by to-one navigation from node.
*/
protected void gatherReachableOldAcyclicNodes(@NonNull BasicPartition partition, @NonNull Set<@NonNull Node> checkableOldNodes, @NonNull Node node) {
if (partition.hasNode(node) || checkableOldNodes.contains(node) || mappingPartitioner.isCyclic(node)) {
return;
}
if ((node.isHead() || node.isOld())) {
// The QVTc SimpleUML2RDBMS uses 'poor style' of multi-child to parent, rather than multiple child-to-parent.
// this leads to a redundant Set node in classComplexAttributes<<local>>, which could be pruned by
// excluding predicated collection-type nodes. But it's a not-essential fudge. The true fix is
// 'good style' or auto-conversion to 'good style' or multi-stage local-predication.
// if (!(node.isDataType() && node.isPredicated())) {
checkableOldNodes.add(node);
for (@NonNull Edge edge : QVTscheduleUtil.getOutgoingEdges(node)) {
if (edge.isNavigation() && edge.isOld()) {
Node targetNode = QVTscheduleUtil.getTargetNode(edge);
gatherReachableOldAcyclicNodes(partition, checkableOldNodes, targetNode);
}
}
// }
}
}
@Override
protected @NonNull Iterable<@NonNull Node> getReachabilityRootNodes() {
Iterable<@NonNull Node> headNodes = QVTscheduleUtil.getHeadNodes(mappingPartitioner.getRegion());
Iterable<@NonNull Node> traceNodes = mappingPartitioner.getTraceNodes();
List<@NonNull Node> rootNodes = new ArrayList<>();
for (@NonNull Node headNode : useActivators ? traceNodes : headNodes) {
if (!headNode.isDependency()) {
rootNodes.add(headNode);
}
}
for (@NonNull Node constantInputNode : mappingPartitioner.getConstantInputNodes()) {
rootNodes.add(constantInputNode);
}
return rootNodes;
}
public boolean hasSharedEdges() {
return sharedEdges != null;
}
protected void initializePartition(@NonNull BasicPartitionAnalysis partitionAnalysis) {
BasicPartition partition = partitionAnalysis.getPartition();
// this.traceNode = mappingPartitioner.getTraceNode();
// boolean useActivators = useActivators;
Set<@NonNull Node> originalHeadNodes = Sets.newHashSet(QVTscheduleUtil.getHeadNodes(mappingPartitioner.getRegion()));
Node dispatchNode = mappingPartitioner.basicGetDispatchNode();
//
// The realized middle (trace) nodes become speculation nodes.
//
if (!hasSynthesizedTrace) {
for (@NonNull Node traceNode : mappingPartitioner.getTraceNodes()) {
addNode(partition, traceNode, Role.SPECULATION);
}
}
//
// For a no-override top relation the realized middle (trace) nodes become speculated nodes.
// For an override top relation the predicated middle (trace) nodes become speculated nodes.
// For a non-top relation the predicated middle (trace) nodes become speculated nodes.
//
for (@NonNull Node traceNode : executionNodes) {
addNode(partition, traceNode, Role.PREDICATED); //, Role.SPECULATED);
}
//
// For an override relation the predicated middle dispatch nodes become speculated nodes.
//
Node dispatchNode2 = dispatchNode;
if (dispatchNode2 != null) {
assert dispatchNode2.isPredicated();
addNode(partition, dispatchNode2); //, Role.SPECULATED);
}
//
// Predicate top-when calls output edges.
//
for (@NonNull Node realizedWhenNode : realizedWhenNodes) {
addNode(partition, realizedWhenNode, Role.PREDICATED);
for (@NonNull Edge incomingEdge : QVTscheduleUtil.getIncomingEdges(realizedWhenNode)) {
Node argumentNode = QVTscheduleUtil.getSourceNode(incomingEdge);
if (scheduleManager.isOutput(argumentNode)) {
addNode(partition, argumentNode); //, Role.SPECULATED);
}
}
}
//
// Prime all shared edges
//
if (sharedEdges != null) {
for (@NonNull Edge edge : sharedEdges) {
addNode(partition, QVTscheduleUtil.getSourceNode(edge));
addNode(partition, QVTscheduleUtil.getTargetNode(edge));
addEdge(partition, edge, Role.PREDICATED);
}
}
//
// All old nodes reachable from heads that are not part of cycles are copied to the speculation guard.
// NB. Unreachable loaded nodes are effectively predicates and so are deferred.
//
Set<@NonNull Node> checkableOldNodes = new HashSet<>();
for (@NonNull Node node : originalHeadNodes) {
gatherReachableOldAcyclicNodes(partition, checkableOldNodes, node);
}
if (dispatchNode != null) {
for (@NonNull Edge edge : QVTscheduleUtil.getIncomingEdges(dispatchNode)) {
assert !edge.isCast();
if (edge.isNavigation() && edge.isOld()) {
Node sourceNode = QVTscheduleUtil.getSourceNode(edge);
gatherReachableOldAcyclicNodes(partition, checkableOldNodes, sourceNode);
}
}
}
for (@NonNull Node node : checkableOldNodes) {
if (hasSynthesizedTrace) {
boolean isCyclicCorollary = transformationAnalysis.isCorollary(node) && mappingPartitioner.isCyclic(node); // waiting for a cyclic corollary could deadlock
// boolean isPredicated = node.isPredicated();
// boolean isMatched = node.isMatched();
// boolean isUnconditional = node.isUnconditional();
Utility utility = node.getUtility();
boolean isWeaklyMatched = utility == Utility.WEAKLY_MATCHED;
boolean isTraced = isTraced(node, executionNodes);
if (!isCyclicCorollary && (isTraced || isWeaklyMatched)) {
addNode(partition, node);
}
}
else {
addNode(partition, node);
}
}
//
// The localSuccess nodes are realized to track speculating success.
//
if (hasSynthesizedTrace) {
resolveSuccessNodes(partition);
}
//
// Add the outstanding predicates that can be checked by this partition.
//
// resolveTrueNodes();
//
// Ensure that the predecessors of each node are included in the partition.
//
resolvePrecedingNodes(partitionAnalysis);
//
// Ensure that re-used trace classes do not lead to ambiguous mappings.
//
resolveDisambiguations(partition);
//
// Join up the edges.
//
resolveEdges(partitionAnalysis);
}
/**
* Return true if edge is available for use by this partition.
* The override implementation returns true for all constant and loaded edges.
*/
// @Override
// protected boolean isAvailable(@NonNull Edge edge) {
// return edge.isConstant() || edge.isLoaded();
// }
/**
* Return true if node is available for use by this partition.
* The override implementation returns true for all constant and loaded nodes.
*/
@Override
protected boolean isAvailable(@NonNull BasicPartition partition, @NonNull Node node) {
return node.isConstant() || node.isLoaded();
}
protected boolean isTraced(@NonNull Node node, @NonNull Iterable<@NonNull Node> executionNodes) {
for (@NonNull Edge edge : QVTscheduleUtil.getIncomingEdges(node)) {
assert !edge.isCast();
if (edge.isNavigation()) {
Node sourceNode = QVTscheduleUtil.getSourceNode(edge);
if (Iterables.contains(executionNodes, sourceNode)) {
return true;
}
}
}
return false;
}
@Override
protected @Nullable Role resolveEdgeRole(@NonNull Role sourceNodeRole, @NonNull Edge edge, @NonNull Role targetNodeRole) {
if (sharedEdgeTargets != null) { // No more edges to phantom shared edge target
Node sourceNode = QVTscheduleUtil.getSourceNode(edge);
Node targetNode = QVTscheduleUtil.getTargetNode(edge);
if (Iterables.contains(sharedEdgeTargets, sourceNode) || Iterables.contains(sharedEdgeTargets, targetNode)) {
return null;
}
}
Role edgeRole = QVTscheduleUtil.getEdgeRole(edge);
if (edgeRole == Role.REALIZED) {
if (mappingPartitioner.hasRealizedEdge(edge)) {
edgeRole = Role.PREDICATED;
}
else if (dispatchNode == edge.getSourceNode()) {
edgeRole = null; // Suppress Diaptach.result assignment
}
}
return edgeRole;
}
protected void resolveSuccessNodes(@NonNull BasicPartition partition) {
for (@NonNull Node traceNode : executionNodes) {
Node localSuccessNode = mappingPartitioner.getLocalSuccessNode(traceNode);
addNode(partition, localSuccessNode, Role.REALIZED);
}
}
}