blob: 18b62e6aada3a2f9efad6d1eee221a5db7e48896 [file] [log] [blame]
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.CallGraphBuilderBase.CycleEliminator.CycleEliminationResult;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.function.Predicate;
abstract class CallGraphBuilderBase {
final AppView<AppInfoWithLiveness> appView;
private final FieldAccessInfoCollection<?> fieldAccessInfoCollection;
final Map<DexMethod, Node> nodes = new IdentityHashMap<>();
private final Map<DexMethod, Set<DexEncodedMethod>> possibleTargetsCache =
new ConcurrentHashMap<>();
CallGraphBuilderBase(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
this.fieldAccessInfoCollection = appView.appInfo().getFieldAccessInfoCollection();
}
public CallGraph build(ExecutorService executorService, Timing timing) throws ExecutionException {
timing.begin("Build IR processing order constraints");
timing.begin("Build call graph");
populateGraph(executorService);
assert verifyNoRedundantFieldReadEdges();
timing.end();
assert verifyAllMethodsWithCodeExists();
appView.withGeneratedMessageLiteBuilderShrinker(
shrinker -> shrinker.preprocessCallGraphBeforeCycleElimination(nodes));
timing.begin("Cycle elimination");
// Sort the nodes for deterministic cycle elimination.
Set<Node> nodesWithDeterministicOrder = Sets.newTreeSet(nodes.values());
CycleEliminator cycleEliminator = new CycleEliminator();
CycleEliminationResult cycleEliminationResult =
cycleEliminator.breakCycles(nodesWithDeterministicOrder);
timing.end();
timing.end();
assert cycleEliminator.breakCycles(nodesWithDeterministicOrder).numberOfRemovedCallEdges()
== 0; // The cycles should be gone.
return new CallGraph(nodesWithDeterministicOrder, cycleEliminationResult);
}
abstract void populateGraph(ExecutorService executorService) throws ExecutionException;
/** Verify that there are no field read edges in the graph if there is also a call graph edge. */
private boolean verifyNoRedundantFieldReadEdges() {
for (Node writer : nodes.values()) {
for (Node reader : writer.getReadersWithDeterministicOrder()) {
assert !writer.hasCaller(reader);
}
}
return true;
}
Node getOrCreateNode(DexEncodedMethod method) {
synchronized (nodes) {
return nodes.computeIfAbsent(method.method, ignore -> new Node(method));
}
}
abstract boolean verifyAllMethodsWithCodeExists();
class InvokeExtractor extends UseRegistry {
private final Node currentMethod;
private final Predicate<DexEncodedMethod> targetTester;
InvokeExtractor(Node currentMethod, Predicate<DexEncodedMethod> targetTester) {
super(appView.dexItemFactory());
this.currentMethod = currentMethod;
this.targetTester = targetTester;
}
private void addClassInitializerTarget(DexProgramClass clazz) {
assert clazz != null;
if (clazz.hasClassInitializer()) {
addCallEdge(clazz.getClassInitializer(), false);
}
}
private void addClassInitializerTarget(DexType type) {
assert type.isClassType();
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
if (clazz != null) {
addClassInitializerTarget(clazz);
}
}
private void addCallEdge(DexEncodedMethod callee, boolean likelySpuriousCallEdge) {
if (!targetTester.test(callee)) {
return;
}
if (callee.accessFlags.isAbstract()) {
// Not a valid target.
return;
}
if (appView.appInfo().isPinned(callee.method)) {
// Since the callee is kept, we cannot inline it into the caller, and we also cannot collect
// any optimization info for the method. Therefore, we drop the call edge to reduce the
// total number of call graph edges, which should lead to fewer call graph cycles.
return;
}
assert callee.isProgramMethod(appView);
getOrCreateNode(callee).addCallerConcurrently(currentMethod, likelySpuriousCallEdge);
}
private void addFieldReadEdge(DexEncodedMethod writer) {
assert !writer.accessFlags.isAbstract();
if (!targetTester.test(writer)) {
return;
}
assert writer.isProgramMethod(appView);
getOrCreateNode(writer).addReaderConcurrently(currentMethod);
}
private void processInvoke(Invoke.Type originalType, DexMethod originalMethod) {
DexEncodedMethod source = currentMethod.method;
DexMethod context = source.method;
GraphLenseLookupResult result =
appView.graphLense().lookupMethod(originalMethod, context, originalType);
DexMethod method = result.getMethod();
Invoke.Type type = result.getType();
if (type == Invoke.Type.INTERFACE || type == Invoke.Type.VIRTUAL) {
// For virtual and interface calls add all potential targets that could be called.
ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
DexEncodedMethod target = resolutionResult.getSingleTarget();
if (target != null) {
processInvokeWithDynamicDispatch(type, target, context.holder);
}
} else {
DexEncodedMethod singleTarget =
appView.appInfo().lookupSingleTarget(type, method, context.holder, appView);
if (singleTarget != null) {
assert !source.accessFlags.isBridge() || singleTarget != currentMethod.method;
DexProgramClass clazz =
asProgramClassOrNull(appView.definitionFor(singleTarget.holder()));
if (clazz != null) {
// For static invokes, the class could be initialized.
if (type == Invoke.Type.STATIC) {
addClassInitializerTarget(clazz);
}
addCallEdge(singleTarget, false);
}
}
}
}
private void processInvokeWithDynamicDispatch(
Invoke.Type type, DexEncodedMethod encodedTarget, DexType context) {
DexMethod target = encodedTarget.method;
DexClass clazz = appView.definitionFor(target.holder);
if (clazz == null) {
assert false : "Unable to lookup holder of `" + target.toSourceString() + "`";
return;
}
if (!appView.options().testing.addCallEdgesForLibraryInvokes) {
if (clazz.isLibraryClass()) {
// Likely to have many possible targets.
return;
}
}
boolean isInterface = type == Invoke.Type.INTERFACE;
Set<DexEncodedMethod> possibleTargets =
possibleTargetsCache.computeIfAbsent(
target,
method -> {
ResolutionResult resolution =
appView.appInfo().resolveMethod(method.holder, method, isInterface);
if (resolution.isVirtualTarget()) {
LookupResult lookupResult =
resolution.lookupVirtualDispatchTargets(
appView.definitionForProgramType(context), appView.appInfo());
if (lookupResult.isLookupResultSuccess()) {
// TODO(b/150277553): Add lambda methods to the call graph.
Set<DexEncodedMethod> targets = new HashSet<>();
lookupResult
.asLookupResultSuccess()
.forEach(
methodTarget -> targets.add(methodTarget.getMethod()),
lambdaTarget -> {
assert false;
});
return targets;
}
}
return null;
});
if (possibleTargets != null) {
boolean likelySpuriousCallEdge =
possibleTargets.size() >= appView.options().callGraphLikelySpuriousCallEdgeThreshold;
for (DexEncodedMethod possibleTarget : possibleTargets) {
if (possibleTarget.isProgramMethod(appView)) {
addCallEdge(possibleTarget, likelySpuriousCallEdge);
}
}
}
}
private void processFieldRead(DexField field) {
if (!field.holder.isClassType()) {
return;
}
DexEncodedField encodedField = appView.appInfo().resolveField(field);
if (encodedField == null || appView.appInfo().isPinned(encodedField.field)) {
return;
}
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(encodedField.holder()));
if (clazz == null) {
return;
}
// Each static field access implicitly triggers the class initializer.
if (encodedField.isStatic()) {
addClassInitializerTarget(clazz);
}
FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.field);
if (fieldAccessInfo != null) {
if (fieldAccessInfo.getNumberOfWriteContexts() == 1) {
fieldAccessInfo.forEachWriteContext(this::addFieldReadEdge);
}
}
}
private void processFieldWrite(DexField field) {
if (field.holder.isClassType()) {
DexEncodedField encodedField = appView.appInfo().resolveField(field);
if (encodedField != null && encodedField.isStatic()) {
// Each static field access implicitly triggers the class initializer.
addClassInitializerTarget(field.holder);
}
}
}
private void processInitClass(DexType type) {
DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
if (clazz == null) {
assert false;
return;
}
addClassInitializerTarget(clazz);
}
@Override
public boolean registerInitClass(DexType clazz) {
processInitClass(clazz);
return false;
}
@Override
public boolean registerInvokeVirtual(DexMethod method) {
processInvoke(Invoke.Type.VIRTUAL, method);
return false;
}
@Override
public boolean registerInvokeDirect(DexMethod method) {
processInvoke(Invoke.Type.DIRECT, method);
return false;
}
@Override
public boolean registerInvokeStatic(DexMethod method) {
processInvoke(Invoke.Type.STATIC, method);
return false;
}
@Override
public boolean registerInvokeInterface(DexMethod method) {
processInvoke(Invoke.Type.INTERFACE, method);
return false;
}
@Override
public boolean registerInvokeSuper(DexMethod method) {
processInvoke(Invoke.Type.SUPER, method);
return false;
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
processFieldRead(field);
return false;
}
@Override
public boolean registerInstanceFieldWrite(DexField field) {
processFieldWrite(field);
return false;
}
@Override
public boolean registerNewInstance(DexType type) {
if (type.isClassType()) {
addClassInitializerTarget(type);
}
return false;
}
@Override
public boolean registerStaticFieldRead(DexField field) {
processFieldRead(field);
return false;
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
processFieldWrite(field);
return false;
}
@Override
public boolean registerTypeReference(DexType type) {
return false;
}
@Override
public void registerCallSite(DexCallSite callSite) {
registerMethodHandle(
callSite.bootstrapMethod, MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
}
}
static class CycleEliminator {
static final String CYCLIC_FORCE_INLINING_MESSAGE =
"Unable to satisfy force inlining constraints due to cyclic force inlining";
private static class CallEdge {
private final Node caller;
private final Node callee;
CallEdge(Node caller, Node callee) {
this.caller = caller;
this.callee = callee;
}
}
static class StackEntryInfo {
final int index;
final Node predecessor;
boolean processed;
StackEntryInfo(int index, Node predecessor) {
this.index = index;
this.predecessor = predecessor;
}
}
static class CycleEliminationResult {
private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges;
CycleEliminationResult(Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges) {
this.removedCallEdges = removedCallEdges;
}
void forEachRemovedCaller(DexEncodedMethod callee, Consumer<DexEncodedMethod> fn) {
removedCallEdges.getOrDefault(callee, ImmutableSet.of()).forEach(fn);
}
int numberOfRemovedCallEdges() {
int numberOfRemovedCallEdges = 0;
for (Set<DexEncodedMethod> nodes : removedCallEdges.values()) {
numberOfRemovedCallEdges += nodes.size();
}
return numberOfRemovedCallEdges;
}
}
// DFS stack.
private Deque<Node> stack = new ArrayDeque<>();
// Nodes on the DFS stack.
private Map<Node, StackEntryInfo> stackEntryInfo = new IdentityHashMap<>();
// Subset of the DFS stack, where the nodes on the stack satisfy that the edge from the
// predecessor to the node itself is a field read edge.
//
// This stack is used to efficiently compute if there is a field read edge inside a cycle when
// a cycle is found.
private Deque<Node> writerStack = new ArrayDeque<>();
// Set of nodes that have been visited entirely.
private Set<Node> marked = Sets.newIdentityHashSet();
// Call edges that should be removed when the caller has been processed. These are not removed
// directly since that would lead to ConcurrentModificationExceptions.
private Map<Node, Set<Node>> calleesToBeRemoved = new IdentityHashMap<>();
// Field read edges that should be removed when the reader has been processed. These are not
// removed directly since that would lead to ConcurrentModificationExceptions.
private Map<Node, Set<Node>> writersToBeRemoved = new IdentityHashMap<>();
// Mapping from callee to the set of callers that were removed from the callee.
private Map<DexEncodedMethod, Set<DexEncodedMethod>> removedCallEdges = new IdentityHashMap<>();
// Set of nodes from which cycle elimination must be rerun to ensure that all cycles will be
// removed.
private LinkedHashSet<Node> revisit = new LinkedHashSet<>();
CycleEliminationResult breakCycles(Collection<Node> roots) {
// Break cycles in this call graph by removing edges causing cycles. We do this in a fixpoint
// because the algorithm does not guarantee that all cycles will be removed from the graph
// when we remove an edge in the middle of a cycle that contains another cycle.
do {
traverse(roots);
roots = revisit;
prepareForNewTraversal();
} while (!roots.isEmpty());
CycleEliminationResult result = new CycleEliminationResult(removedCallEdges);
if (Log.ENABLED) {
Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedCallEdges());
}
reset();
return result;
}
private void prepareForNewTraversal() {
assert calleesToBeRemoved.isEmpty();
assert stack.isEmpty();
assert stackEntryInfo.isEmpty();
assert writersToBeRemoved.isEmpty();
assert writerStack.isEmpty();
marked.clear();
revisit = new LinkedHashSet<>();
}
private void reset() {
assert marked.isEmpty();
assert revisit.isEmpty();
assert stack.isEmpty();
assert stackEntryInfo.isEmpty();
assert writerStack.isEmpty();
removedCallEdges = new IdentityHashMap<>();
}
private static class WorkItem {
boolean isNode() {
return false;
}
NodeWorkItem asNode() {
return null;
}
boolean isIterator() {
return false;
}
IteratorWorkItem asIterator() {
return null;
}
}
private static class NodeWorkItem extends WorkItem {
private final Node node;
NodeWorkItem(Node node) {
this.node = node;
}
@Override
boolean isNode() {
return true;
}
@Override
NodeWorkItem asNode() {
return this;
}
}
private static class IteratorWorkItem extends WorkItem {
private final Node callerOrReader;
private final Iterator<Node> calleesAndWriters;
IteratorWorkItem(Node callerOrReader, Iterator<Node> calleesAndWriters) {
this.callerOrReader = callerOrReader;
this.calleesAndWriters = calleesAndWriters;
}
@Override
boolean isIterator() {
return true;
}
@Override
IteratorWorkItem asIterator() {
return this;
}
}
private void traverse(Collection<Node> roots) {
Deque<WorkItem> workItems = new ArrayDeque<>(roots.size());
for (Node node : roots) {
workItems.addLast(new NodeWorkItem(node));
}
while (!workItems.isEmpty()) {
WorkItem workItem = workItems.removeFirst();
if (workItem.isNode()) {
Node node = workItem.asNode().node;
if (marked.contains(node)) {
// Already visited all nodes that can be reached from this node.
continue;
}
Node predecessor = stack.isEmpty() ? null : stack.peek();
push(node, predecessor);
// The callees and writers must be sorted before calling traverse recursively.
// This ensures that cycles are broken the same way across multiple compilations.
Iterator<Node> calleesAndWriterIterator =
Iterators.concat(
node.getCalleesWithDeterministicOrder().iterator(),
node.getWritersWithDeterministicOrder().iterator());
workItems.addFirst(new IteratorWorkItem(node, calleesAndWriterIterator));
} else {
assert workItem.isIterator();
IteratorWorkItem iteratorWorkItem = workItem.asIterator();
Node newCallerOrReader =
iterateCalleesAndWriters(
iteratorWorkItem.calleesAndWriters, iteratorWorkItem.callerOrReader);
if (newCallerOrReader != null) {
// We did not finish the work on this iterator, so add it again.
workItems.addFirst(iteratorWorkItem);
workItems.addFirst(new NodeWorkItem(newCallerOrReader));
} else {
assert !iteratorWorkItem.calleesAndWriters.hasNext();
pop(iteratorWorkItem.callerOrReader);
marked.add(iteratorWorkItem.callerOrReader);
Collection<Node> calleesToBeRemovedFromCaller =
calleesToBeRemoved.remove(iteratorWorkItem.callerOrReader);
if (calleesToBeRemovedFromCaller != null) {
calleesToBeRemovedFromCaller.forEach(
callee -> {
callee.removeCaller(iteratorWorkItem.callerOrReader);
recordCallEdgeRemoval(iteratorWorkItem.callerOrReader, callee);
});
}
Collection<Node> writersToBeRemovedFromReader =
writersToBeRemoved.remove(iteratorWorkItem.callerOrReader);
if (writersToBeRemovedFromReader != null) {
writersToBeRemovedFromReader.forEach(
writer -> writer.removeReader(iteratorWorkItem.callerOrReader));
}
}
}
}
}
private Node iterateCalleesAndWriters(
Iterator<Node> calleeOrWriterIterator, Node callerOrReader) {
while (calleeOrWriterIterator.hasNext()) {
Node calleeOrWriter = calleeOrWriterIterator.next();
StackEntryInfo calleeOrWriterStackEntryInfo = stackEntryInfo.get(calleeOrWriter);
boolean foundCycle = calleeOrWriterStackEntryInfo != null;
if (!foundCycle) {
return calleeOrWriter;
}
// Found a cycle that needs to be eliminated. If it is a field read edge, then remove it
// right away.
boolean isFieldReadEdge = calleeOrWriter.hasReader(callerOrReader);
if (isFieldReadEdge) {
removeFieldReadEdge(callerOrReader, calleeOrWriter);
continue;
}
// Otherwise, it is a call edge. Check if there is a field read edge in the cycle, and if
// so, remove that edge.
if (!writerStack.isEmpty()) {
Node lastKnownWriter = writerStack.peek();
StackEntryInfo lastKnownWriterStackEntryInfo = stackEntryInfo.get(lastKnownWriter);
boolean cycleContainsLastKnownWriter =
lastKnownWriterStackEntryInfo.index > calleeOrWriterStackEntryInfo.index;
if (cycleContainsLastKnownWriter) {
assert verifyCycleSatisfies(
calleeOrWriter,
cycle ->
cycle.contains(lastKnownWriter)
&& cycle.contains(lastKnownWriterStackEntryInfo.predecessor));
if (!lastKnownWriterStackEntryInfo.processed) {
removeFieldReadEdge(lastKnownWriterStackEntryInfo.predecessor, lastKnownWriter);
revisit.add(lastKnownWriter);
lastKnownWriterStackEntryInfo.processed = true;
}
continue;
}
}
// It is a call edge, and the cycle does not contain any field read edges. In this case, we
// remove the call edge if it is safe according to force inlining.
if (callEdgeRemovalIsSafe(callerOrReader, calleeOrWriter)) {
// Break the cycle by removing the edge node->calleeOrWriter.
// Need to remove `calleeOrWriter` from `node.callees` using the iterator to prevent a
// ConcurrentModificationException.
removeCallEdge(callerOrReader, calleeOrWriter);
continue;
}
// The call edge cannot be removed due to force inlining. Find another call edge in the
// cycle that can safely be removed instead.
LinkedList<Node> cycle = extractCycle(calleeOrWriter);
// Break the cycle by finding an edge that can be removed without breaking force
// inlining. If that is not possible, this call fails with a compilation error.
CallEdge edge = findCallEdgeForRemoval(cycle);
// The edge will be null if this cycle has already been eliminated as a result of
// another cycle elimination.
if (edge != null) {
assert callEdgeRemovalIsSafe(edge.caller, edge.callee);
// Break the cycle by removing the edge caller->callee.
removeCallEdge(edge.caller, edge.callee);
}
// Recover the stack.
recoverStack(cycle);
}
return null;
}
private void push(Node node, Node predecessor) {
stack.push(node);
assert !stackEntryInfo.containsKey(node);
stackEntryInfo.put(node, new StackEntryInfo(stack.size() - 1, predecessor));
if (predecessor != null && predecessor.getWritersWithDeterministicOrder().contains(node)) {
writerStack.push(node);
}
}
private void pop(Node node) {
Node popped = stack.pop();
assert popped == node;
assert stackEntryInfo.containsKey(node);
stackEntryInfo.remove(node);
if (writerStack.peek() == popped) {
writerStack.pop();
}
}
private void removeCallEdge(Node caller, Node callee) {
calleesToBeRemoved.computeIfAbsent(caller, ignore -> Sets.newIdentityHashSet()).add(callee);
}
private void removeFieldReadEdge(Node reader, Node writer) {
writersToBeRemoved.computeIfAbsent(reader, ignore -> Sets.newIdentityHashSet()).add(writer);
}
private LinkedList<Node> extractCycle(Node entry) {
LinkedList<Node> cycle = new LinkedList<>();
do {
assert !stack.isEmpty();
cycle.add(stack.pop());
} while (cycle.getLast() != entry);
return cycle;
}
private boolean verifyCycleSatisfies(Node entry, Predicate<LinkedList<Node>> predicate) {
LinkedList<Node> cycle = extractCycle(entry);
assert predicate.test(cycle);
recoverStack(cycle);
return true;
}
private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
Node callee = extractedCycle.getLast();
for (Node caller : extractedCycle) {
if (caller.hasWriter(callee)) {
// Not a call edge.
assert !caller.hasCallee(callee);
assert !callee.hasCaller(caller);
callee = caller;
continue;
}
if (!caller.hasCallee(callee)) {
// No need to break any edges since this cycle has already been broken previously.
assert !callee.hasCaller(caller);
return null;
}
if (callEdgeRemovalIsSafe(caller, callee)) {
return new CallEdge(caller, callee);
}
callee = caller;
}
throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
}
private static boolean callEdgeRemovalIsSafe(Node callerOrReader, Node calleeOrWriter) {
// All call edges where the callee is a method that should be force inlined must be kept,
// to guarantee that the IR converter will process the callee before the caller.
assert calleeOrWriter.hasCaller(callerOrReader);
return !calleeOrWriter.method.getOptimizationInfo().forceInline();
}
private void recordCallEdgeRemoval(Node caller, Node callee) {
removedCallEdges
.computeIfAbsent(callee.method, ignore -> SetUtils.newIdentityHashSet(2))
.add(caller.method);
}
private void recoverStack(LinkedList<Node> extractedCycle) {
Iterator<Node> descendingIt = extractedCycle.descendingIterator();
while (descendingIt.hasNext()) {
stack.push(descendingIt.next());
}
}
}
}