1 /*
   2  * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.phases.common.inlining.info;
  24 
  25 import java.util.ArrayList;
  26 import java.util.List;
  27 
  28 import org.graalvm.compiler.core.common.type.StampFactory;
  29 import org.graalvm.compiler.debug.Debug;
  30 import org.graalvm.compiler.graph.Node;
  31 import org.graalvm.compiler.nodes.AbstractBeginNode;
  32 import org.graalvm.compiler.nodes.AbstractMergeNode;
  33 import org.graalvm.compiler.nodes.BeginNode;
  34 import org.graalvm.compiler.nodes.CallTargetNode.InvokeKind;
  35 import org.graalvm.compiler.nodes.DeoptimizeNode;
  36 import org.graalvm.compiler.nodes.EndNode;
  37 import org.graalvm.compiler.nodes.FixedNode;
  38 import org.graalvm.compiler.nodes.FixedWithNextNode;
  39 import org.graalvm.compiler.nodes.FrameState;
  40 import org.graalvm.compiler.nodes.Invoke;
  41 import org.graalvm.compiler.nodes.InvokeWithExceptionNode;
  42 import org.graalvm.compiler.nodes.MergeNode;
  43 import org.graalvm.compiler.nodes.PhiNode;
  44 import org.graalvm.compiler.nodes.PiNode;
  45 import org.graalvm.compiler.nodes.StructuredGraph;
  46 import org.graalvm.compiler.nodes.ValueNode;
  47 import org.graalvm.compiler.nodes.ValuePhiNode;
  48 import org.graalvm.compiler.nodes.extended.LoadHubNode;
  49 import org.graalvm.compiler.nodes.java.ExceptionObjectNode;
  50 import org.graalvm.compiler.nodes.java.MethodCallTargetNode;
  51 import org.graalvm.compiler.nodes.java.TypeSwitchNode;
  52 import org.graalvm.compiler.nodes.spi.StampProvider;
  53 import org.graalvm.compiler.nodes.util.GraphUtil;
  54 import org.graalvm.compiler.phases.common.inlining.InliningUtil;
  55 import org.graalvm.compiler.phases.common.inlining.info.elem.Inlineable;
  56 import org.graalvm.compiler.phases.util.Providers;
  57 import org.graalvm.util.EconomicSet;
  58 import org.graalvm.util.Equivalence;
  59 
  60 import jdk.vm.ci.meta.ConstantReflectionProvider;
  61 import jdk.vm.ci.meta.DeoptimizationAction;
  62 import jdk.vm.ci.meta.DeoptimizationReason;
  63 import jdk.vm.ci.meta.JavaKind;
  64 import jdk.vm.ci.meta.JavaTypeProfile.ProfiledType;
  65 import jdk.vm.ci.meta.ResolvedJavaMethod;
  66 import jdk.vm.ci.meta.ResolvedJavaType;
  67 
  68 /**
  69  * Polymorphic inlining of m methods with n type checks (n ≥ m) in case that the profiling
  70  * information suggests a reasonable amount of different receiver types and different methods. If an
  71  * unknown type is encountered a deoptimization is triggered.
  72  */
  73 public class MultiTypeGuardInlineInfo extends AbstractInlineInfo {
  74 
  75     private final List<ResolvedJavaMethod> concretes;
  76     private final double[] methodProbabilities;
  77     private final double maximumMethodProbability;
  78     private final ArrayList<Integer> typesToConcretes;
  79     private final ArrayList<ProfiledType> ptypes;
  80     private final double notRecordedTypeProbability;
  81     private final Inlineable[] inlineableElements;
  82 
  83     public MultiTypeGuardInlineInfo(Invoke invoke, ArrayList<ResolvedJavaMethod> concretes, ArrayList<ProfiledType> ptypes, ArrayList<Integer> typesToConcretes, double notRecordedTypeProbability) {
  84         super(invoke);
  85         assert concretes.size() > 0 : "must have at least one method";
  86         assert ptypes.size() == typesToConcretes.size() : "array lengths must match";
  87 
  88         this.concretes = concretes;
  89         this.ptypes = ptypes;
  90         this.typesToConcretes = typesToConcretes;
  91         this.notRecordedTypeProbability = notRecordedTypeProbability;
  92         this.inlineableElements = new Inlineable[concretes.size()];
  93         this.methodProbabilities = computeMethodProbabilities();
  94         this.maximumMethodProbability = maximumMethodProbability();
  95         assert maximumMethodProbability > 0;
  96         assert assertUniqueTypes(ptypes);
  97     }
  98 
  99     private static boolean assertUniqueTypes(ArrayList<ProfiledType> ptypes) {
 100         EconomicSet<ResolvedJavaType> set = EconomicSet.create(Equivalence.DEFAULT);
 101         for (ProfiledType ptype : ptypes) {
 102             set.add(ptype.getType());
 103         }
 104         return set.size() == ptypes.size();
 105     }
 106 
 107     private double[] computeMethodProbabilities() {
 108         double[] result = new double[concretes.size()];
 109         for (int i = 0; i < typesToConcretes.size(); i++) {
 110             int concrete = typesToConcretes.get(i);
 111             double probability = ptypes.get(i).getProbability();
 112             result[concrete] += probability;
 113         }
 114         return result;
 115     }
 116 
 117     private double maximumMethodProbability() {
 118         double max = 0;
 119         for (int i = 0; i < methodProbabilities.length; i++) {
 120             max = Math.max(max, methodProbabilities[i]);
 121         }
 122         return max;
 123     }
 124 
 125     @Override
 126     public int numberOfMethods() {
 127         return concretes.size();
 128     }
 129 
 130     @Override
 131     public ResolvedJavaMethod methodAt(int index) {
 132         assert index >= 0 && index < concretes.size();
 133         return concretes.get(index);
 134     }
 135 
 136     @Override
 137     public Inlineable inlineableElementAt(int index) {
 138         assert index >= 0 && index < concretes.size();
 139         return inlineableElements[index];
 140     }
 141 
 142     @Override
 143     public double probabilityAt(int index) {
 144         return methodProbabilities[index];
 145     }
 146 
 147     @Override
 148     public double relevanceAt(int index) {
 149         return probabilityAt(index) / maximumMethodProbability;
 150     }
 151 
 152     @Override
 153     public void setInlinableElement(int index, Inlineable inlineableElement) {
 154         assert index >= 0 && index < concretes.size();
 155         inlineableElements[index] = inlineableElement;
 156     }
 157 
 158     @Override
 159     public EconomicSet<Node> inline(Providers providers) {
 160         if (hasSingleMethod()) {
 161             return inlineSingleMethod(graph(), providers.getStampProvider(), providers.getConstantReflection());
 162         } else {
 163             return inlineMultipleMethods(graph(), providers);
 164         }
 165     }
 166 
 167     @Override
 168     public boolean shouldInline() {
 169         for (ResolvedJavaMethod method : concretes) {
 170             if (method.shouldBeInlined()) {
 171                 return true;
 172             }
 173         }
 174         return false;
 175     }
 176 
 177     private boolean hasSingleMethod() {
 178         return concretes.size() == 1 && !shouldFallbackToInvoke();
 179     }
 180 
 181     private boolean shouldFallbackToInvoke() {
 182         return notRecordedTypeProbability > 0;
 183     }
 184 
 185     private EconomicSet<Node> inlineMultipleMethods(StructuredGraph graph, Providers providers) {
 186         int numberOfMethods = concretes.size();
 187         FixedNode continuation = invoke.next();
 188 
 189         // setup merge and phi nodes for results and exceptions
 190         AbstractMergeNode returnMerge = graph.add(new MergeNode());
 191         returnMerge.setStateAfter(invoke.stateAfter());
 192 
 193         PhiNode returnValuePhi = null;
 194         if (invoke.asNode().getStackKind() != JavaKind.Void) {
 195             returnValuePhi = graph.addWithoutUnique(new ValuePhiNode(invoke.asNode().stamp().unrestricted(), returnMerge));
 196         }
 197 
 198         AbstractMergeNode exceptionMerge = null;
 199         PhiNode exceptionObjectPhi = null;
 200         if (invoke instanceof InvokeWithExceptionNode) {
 201             InvokeWithExceptionNode invokeWithException = (InvokeWithExceptionNode) invoke;
 202             ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithException.exceptionEdge();
 203 
 204             exceptionMerge = graph.add(new MergeNode());
 205 
 206             FixedNode exceptionSux = exceptionEdge.next();
 207             graph.addBeforeFixed(exceptionSux, exceptionMerge);
 208             exceptionObjectPhi = graph.addWithoutUnique(new ValuePhiNode(StampFactory.forKind(JavaKind.Object), exceptionMerge));
 209             exceptionMerge.setStateAfter(exceptionEdge.stateAfter().duplicateModified(invoke.stateAfter().bci, true, JavaKind.Object, new JavaKind[]{JavaKind.Object},
 210                             new ValueNode[]{exceptionObjectPhi}));
 211         }
 212 
 213         // create one separate block for each invoked method
 214         AbstractBeginNode[] successors = new AbstractBeginNode[numberOfMethods + 1];
 215         for (int i = 0; i < numberOfMethods; i++) {
 216             successors[i] = createInvocationBlock(graph, invoke, returnMerge, returnValuePhi, exceptionMerge, exceptionObjectPhi, true);
 217         }
 218 
 219         // create the successor for an unknown type
 220         FixedNode unknownTypeSux;
 221         if (shouldFallbackToInvoke()) {
 222             unknownTypeSux = createInvocationBlock(graph, invoke, returnMerge, returnValuePhi, exceptionMerge, exceptionObjectPhi, false);
 223         } else {
 224             unknownTypeSux = graph.add(new DeoptimizeNode(DeoptimizationAction.InvalidateReprofile, DeoptimizationReason.TypeCheckedInliningViolated));
 225         }
 226         successors[successors.length - 1] = BeginNode.begin(unknownTypeSux);
 227 
 228         // replace the invoke exception edge
 229         if (invoke instanceof InvokeWithExceptionNode) {
 230             InvokeWithExceptionNode invokeWithExceptionNode = (InvokeWithExceptionNode) invoke;
 231             ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithExceptionNode.exceptionEdge();
 232             exceptionEdge.replaceAtUsages(exceptionObjectPhi);
 233             exceptionEdge.setNext(null);
 234             GraphUtil.killCFG(invokeWithExceptionNode.exceptionEdge());
 235         }
 236 
 237         assert invoke.asNode().isAlive();
 238 
 239         // replace the invoke with a switch on the type of the actual receiver
 240         boolean methodDispatch = createDispatchOnTypeBeforeInvoke(graph, successors, false, providers.getStampProvider(), providers.getConstantReflection());
 241 
 242         assert invoke.next() == continuation;
 243         invoke.setNext(null);
 244         returnMerge.setNext(continuation);
 245         if (returnValuePhi != null) {
 246             invoke.asNode().replaceAtUsages(returnValuePhi);
 247         }
 248         invoke.asNode().safeDelete();
 249 
 250         ArrayList<PiNode> replacementNodes = new ArrayList<>();
 251 
 252         // prepare the anchors for the invokes
 253         for (int i = 0; i < numberOfMethods; i++) {
 254             AbstractBeginNode node = successors[i];
 255             Invoke invokeForInlining = (Invoke) node.next();
 256 
 257             ResolvedJavaType commonType;
 258             if (methodDispatch) {
 259                 commonType = concretes.get(i).getDeclaringClass();
 260             } else {
 261                 commonType = getLeastCommonType(i);
 262             }
 263 
 264             ValueNode receiver = ((MethodCallTargetNode) invokeForInlining.callTarget()).receiver();
 265             boolean exact = (getTypeCount(i) == 1 && !methodDispatch);
 266             PiNode anchoredReceiver = InliningUtil.createAnchoredReceiver(graph, node, commonType, receiver, exact);
 267             invokeForInlining.callTarget().replaceFirstInput(receiver, anchoredReceiver);
 268 
 269             assert !anchoredReceiver.isDeleted() : anchoredReceiver;
 270             replacementNodes.add(anchoredReceiver);
 271         }
 272         if (shouldFallbackToInvoke()) {
 273             replacementNodes.add(null);
 274         }
 275 
 276         EconomicSet<Node> canonicalizeNodes = EconomicSet.create(Equivalence.DEFAULT);
 277         // do the actual inlining for every invoke
 278         for (int i = 0; i < numberOfMethods; i++) {
 279             Invoke invokeForInlining = (Invoke) successors[i].next();
 280             canonicalizeNodes.addAll(doInline(i, invokeForInlining));
 281         }
 282         if (returnValuePhi != null) {
 283             canonicalizeNodes.add(returnValuePhi);
 284         }
 285         return canonicalizeNodes;
 286     }
 287 
 288     protected EconomicSet<Node> doInline(int index, Invoke invokeForInlining) {
 289         return inline(invokeForInlining, methodAt(index), inlineableElementAt(index), false);
 290     }
 291 
 292     private int getTypeCount(int concreteMethodIndex) {
 293         int count = 0;
 294         for (int i = 0; i < typesToConcretes.size(); i++) {
 295             if (typesToConcretes.get(i) == concreteMethodIndex) {
 296                 count++;
 297             }
 298         }
 299         return count;
 300     }
 301 
 302     private ResolvedJavaType getLeastCommonType(int concreteMethodIndex) {
 303         ResolvedJavaType commonType = null;
 304         for (int i = 0; i < typesToConcretes.size(); i++) {
 305             if (typesToConcretes.get(i) == concreteMethodIndex) {
 306                 if (commonType == null) {
 307                     commonType = ptypes.get(i).getType();
 308                 } else {
 309                     commonType = commonType.findLeastCommonAncestor(ptypes.get(i).getType());
 310                 }
 311             }
 312         }
 313         assert commonType != null;
 314         return commonType;
 315     }
 316 
 317     private ResolvedJavaType getLeastCommonType() {
 318         ResolvedJavaType result = getLeastCommonType(0);
 319         for (int i = 1; i < concretes.size(); i++) {
 320             result = result.findLeastCommonAncestor(getLeastCommonType(i));
 321         }
 322         return result;
 323     }
 324 
 325     private EconomicSet<Node> inlineSingleMethod(StructuredGraph graph, StampProvider stampProvider, ConstantReflectionProvider constantReflection) {
 326         assert concretes.size() == 1 && inlineableElements.length == 1 && ptypes.size() > 1 && !shouldFallbackToInvoke() && notRecordedTypeProbability == 0;
 327 
 328         AbstractBeginNode calleeEntryNode = graph.add(new BeginNode());
 329 
 330         AbstractBeginNode unknownTypeSux = createUnknownTypeSuccessor(graph);
 331         AbstractBeginNode[] successors = new AbstractBeginNode[]{calleeEntryNode, unknownTypeSux};
 332         createDispatchOnTypeBeforeInvoke(graph, successors, false, stampProvider, constantReflection);
 333 
 334         calleeEntryNode.setNext(invoke.asNode());
 335 
 336         return inline(invoke, methodAt(0), inlineableElementAt(0), false);
 337     }
 338 
 339     private boolean createDispatchOnTypeBeforeInvoke(StructuredGraph graph, AbstractBeginNode[] successors, boolean invokeIsOnlySuccessor, StampProvider stampProvider,
 340                     ConstantReflectionProvider constantReflection) {
 341         assert ptypes.size() >= 1;
 342         ValueNode nonNullReceiver = InliningUtil.nonNullReceiver(invoke);
 343         LoadHubNode hub = graph.unique(new LoadHubNode(stampProvider, nonNullReceiver));
 344 
 345         Debug.log("Type switch with %d types", concretes.size());
 346 
 347         ResolvedJavaType[] keys = new ResolvedJavaType[ptypes.size()];
 348         double[] keyProbabilities = new double[ptypes.size() + 1];
 349         int[] keySuccessors = new int[ptypes.size() + 1];
 350         double totalProbability = notRecordedTypeProbability;
 351         for (int i = 0; i < ptypes.size(); i++) {
 352             keys[i] = ptypes.get(i).getType();
 353             keyProbabilities[i] = ptypes.get(i).getProbability();
 354             totalProbability += keyProbabilities[i];
 355             keySuccessors[i] = invokeIsOnlySuccessor ? 0 : typesToConcretes.get(i);
 356             assert keySuccessors[i] < successors.length - 1 : "last successor is the unknownTypeSux";
 357         }
 358         keyProbabilities[keyProbabilities.length - 1] = notRecordedTypeProbability;
 359         keySuccessors[keySuccessors.length - 1] = successors.length - 1;
 360 
 361         // Normalize the probabilities.
 362         for (int i = 0; i < keyProbabilities.length; i++) {
 363             keyProbabilities[i] /= totalProbability;
 364         }
 365 
 366         TypeSwitchNode typeSwitch = graph.add(new TypeSwitchNode(hub, successors, keys, keyProbabilities, keySuccessors, constantReflection));
 367         FixedWithNextNode pred = (FixedWithNextNode) invoke.asNode().predecessor();
 368         pred.setNext(typeSwitch);
 369         return false;
 370     }
 371 
 372     private static AbstractBeginNode createInvocationBlock(StructuredGraph graph, Invoke invoke, AbstractMergeNode returnMerge, PhiNode returnValuePhi, AbstractMergeNode exceptionMerge,
 373                     PhiNode exceptionObjectPhi, boolean useForInlining) {
 374         Invoke duplicatedInvoke = duplicateInvokeForInlining(graph, invoke, exceptionMerge, exceptionObjectPhi, useForInlining);
 375         AbstractBeginNode calleeEntryNode = graph.add(new BeginNode());
 376         calleeEntryNode.setNext(duplicatedInvoke.asNode());
 377 
 378         EndNode endNode = graph.add(new EndNode());
 379         duplicatedInvoke.setNext(endNode);
 380         returnMerge.addForwardEnd(endNode);
 381 
 382         if (returnValuePhi != null) {
 383             returnValuePhi.addInput(duplicatedInvoke.asNode());
 384         }
 385         return calleeEntryNode;
 386     }
 387 
 388     private static Invoke duplicateInvokeForInlining(StructuredGraph graph, Invoke invoke, AbstractMergeNode exceptionMerge, PhiNode exceptionObjectPhi, boolean useForInlining) {
 389         Invoke result = (Invoke) invoke.asNode().copyWithInputs();
 390         Node callTarget = result.callTarget().copyWithInputs();
 391         result.asNode().replaceFirstInput(result.callTarget(), callTarget);
 392         result.setUseForInlining(useForInlining);
 393 
 394         JavaKind kind = invoke.asNode().getStackKind();
 395         if (kind != JavaKind.Void) {
 396             FrameState stateAfter = invoke.stateAfter();
 397             stateAfter = stateAfter.duplicate(stateAfter.bci);
 398             stateAfter.replaceFirstInput(invoke.asNode(), result.asNode());
 399             result.setStateAfter(stateAfter);
 400         }
 401 
 402         if (invoke instanceof InvokeWithExceptionNode) {
 403             assert exceptionMerge != null && exceptionObjectPhi != null;
 404 
 405             InvokeWithExceptionNode invokeWithException = (InvokeWithExceptionNode) invoke;
 406             ExceptionObjectNode exceptionEdge = (ExceptionObjectNode) invokeWithException.exceptionEdge();
 407             FrameState stateAfterException = exceptionEdge.stateAfter();
 408 
 409             ExceptionObjectNode newExceptionEdge = (ExceptionObjectNode) exceptionEdge.copyWithInputs();
 410             // set new state (pop old exception object, push new one)
 411             newExceptionEdge.setStateAfter(stateAfterException.duplicateModified(JavaKind.Object, JavaKind.Object, newExceptionEdge));
 412 
 413             EndNode endNode = graph.add(new EndNode());
 414             newExceptionEdge.setNext(endNode);
 415             exceptionMerge.addForwardEnd(endNode);
 416             exceptionObjectPhi.addInput(newExceptionEdge);
 417 
 418             ((InvokeWithExceptionNode) result).setExceptionEdge(newExceptionEdge);
 419         }
 420         return result;
 421     }
 422 
 423     @Override
 424     public void tryToDevirtualizeInvoke(Providers providers) {
 425         if (hasSingleMethod()) {
 426             devirtualizeWithTypeSwitch(graph(), InvokeKind.Special, concretes.get(0), providers.getStampProvider(), providers.getConstantReflection());
 427         } else {
 428             tryToDevirtualizeMultipleMethods(graph(), providers.getStampProvider(), providers.getConstantReflection());
 429         }
 430     }
 431 
 432     private void tryToDevirtualizeMultipleMethods(StructuredGraph graph, StampProvider stampProvider, ConstantReflectionProvider constantReflection) {
 433         MethodCallTargetNode methodCallTarget = (MethodCallTargetNode) invoke.callTarget();
 434         if (methodCallTarget.invokeKind() == InvokeKind.Interface) {
 435             ResolvedJavaMethod targetMethod = methodCallTarget.targetMethod();
 436             ResolvedJavaType leastCommonType = getLeastCommonType();
 437             ResolvedJavaType contextType = invoke.getContextType();
 438             // check if we have a common base type that implements the interface -> in that case
 439             // we have a vtable entry for the interface method and can use a less expensive
 440             // virtual call
 441             if (!leastCommonType.isInterface() && targetMethod.getDeclaringClass().isAssignableFrom(leastCommonType)) {
 442                 ResolvedJavaMethod baseClassTargetMethod = leastCommonType.resolveConcreteMethod(targetMethod, contextType);
 443                 if (baseClassTargetMethod != null) {
 444                     devirtualizeWithTypeSwitch(graph, InvokeKind.Virtual, leastCommonType.resolveConcreteMethod(targetMethod, contextType), stampProvider, constantReflection);
 445                 }
 446             }
 447         }
 448     }
 449 
 450     private void devirtualizeWithTypeSwitch(StructuredGraph graph, InvokeKind kind, ResolvedJavaMethod target, StampProvider stampProvider, ConstantReflectionProvider constantReflection) {
 451         AbstractBeginNode invocationEntry = graph.add(new BeginNode());
 452         AbstractBeginNode unknownTypeSux = createUnknownTypeSuccessor(graph);
 453         AbstractBeginNode[] successors = new AbstractBeginNode[]{invocationEntry, unknownTypeSux};
 454         createDispatchOnTypeBeforeInvoke(graph, successors, true, stampProvider, constantReflection);
 455 
 456         invocationEntry.setNext(invoke.asNode());
 457         ValueNode receiver = ((MethodCallTargetNode) invoke.callTarget()).receiver();
 458         PiNode anchoredReceiver = InliningUtil.createAnchoredReceiver(graph, invocationEntry, target.getDeclaringClass(), receiver, false);
 459         invoke.callTarget().replaceFirstInput(receiver, anchoredReceiver);
 460         InliningUtil.replaceInvokeCallTarget(invoke, graph, kind, target);
 461     }
 462 
 463     private static AbstractBeginNode createUnknownTypeSuccessor(StructuredGraph graph) {
 464         return BeginNode.begin(graph.add(new DeoptimizeNode(DeoptimizationAction.InvalidateReprofile, DeoptimizationReason.TypeCheckedInliningViolated)));
 465     }
 466 
 467     @Override
 468     public String toString() {
 469         StringBuilder builder = new StringBuilder(shouldFallbackToInvoke() ? "megamorphic" : "polymorphic");
 470         builder.append(", ");
 471         builder.append(concretes.size());
 472         builder.append(" methods [ ");
 473         for (int i = 0; i < concretes.size(); i++) {
 474             builder.append(concretes.get(i).format("  %H.%n(%p):%r"));
 475         }
 476         builder.append(" ], ");
 477         builder.append(ptypes.size());
 478         builder.append(" type checks [ ");
 479         for (int i = 0; i < ptypes.size(); i++) {
 480             builder.append("  ");
 481             builder.append(ptypes.get(i).getType().getName());
 482             builder.append(ptypes.get(i).getProbability());
 483         }
 484         builder.append(" ]");
 485         return builder.toString();
 486     }
 487 }