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