1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 package com.android.tools.r8.ir.conversion; 5 6 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources; 7 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources; 8 9 import com.android.tools.r8.errors.Unreachable; 10 import com.android.tools.r8.graph.AppInfo; 11 import com.android.tools.r8.graph.AppInfoWithSubtyping; 12 import com.android.tools.r8.graph.Code; 13 import com.android.tools.r8.graph.DexApplication; 14 import com.android.tools.r8.graph.DexApplication.Builder; 15 import com.android.tools.r8.graph.DexEncodedMethod; 16 import com.android.tools.r8.graph.DexItemFactory; 17 import com.android.tools.r8.graph.DexMethod; 18 import com.android.tools.r8.graph.DexProgramClass; 19 import com.android.tools.r8.graph.DexString; 20 import com.android.tools.r8.graph.DexType; 21 import com.android.tools.r8.graph.GraphLense; 22 import com.android.tools.r8.ir.code.IRCode; 23 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter; 24 import com.android.tools.r8.ir.desugar.LambdaRewriter; 25 import com.android.tools.r8.ir.optimize.CodeRewriter; 26 import com.android.tools.r8.ir.optimize.DeadCodeRemover; 27 import com.android.tools.r8.ir.optimize.Inliner; 28 import com.android.tools.r8.ir.optimize.Inliner.Constraint; 29 import com.android.tools.r8.ir.optimize.MemberValuePropagation; 30 import com.android.tools.r8.ir.optimize.Outliner; 31 import com.android.tools.r8.ir.optimize.PeepholeOptimizer; 32 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator; 33 import com.android.tools.r8.ir.regalloc.RegisterAllocator; 34 import com.android.tools.r8.logging.Log; 35 import com.android.tools.r8.utils.CfgPrinter; 36 import com.android.tools.r8.utils.DescriptorUtils; 37 import com.android.tools.r8.utils.InternalOptions; 38 import com.android.tools.r8.utils.ThreadUtils; 39 import com.android.tools.r8.utils.Timing; 40 41 import com.google.common.collect.ImmutableSet; 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Set; 45 import java.util.concurrent.ExecutionException; 46 import java.util.concurrent.ExecutorService; 47 import java.util.concurrent.Executors; 48 import java.util.concurrent.Future; 49 import java.util.function.BiConsumer; 50 51 public class IRConverter { 52 53 public static final int PEEPHOLE_OPTIMIZATION_PASSES = 2; 54 55 private final Timing timing; 56 public final DexApplication application; 57 public final AppInfo appInfo; 58 private final Outliner outliner; 59 private final LambdaRewriter lambdaRewriter; 60 private final InterfaceMethodRewriter interfaceMethodRewriter; 61 private final InternalOptions options; 62 private final CfgPrinter printer; 63 private final GraphLense graphLense; 64 private final CodeRewriter codeRewriter; 65 private final MemberValuePropagation memberValuePropagation; 66 private final LensCodeRewriter lensCodeRewriter; 67 private final Inliner inliner; 68 private CallGraph callGraph; 69 private OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore(); 70 71 private DexString highestSortingString; 72 73 private IRConverter( 74 Timing timing, 75 DexApplication application, 76 AppInfo appInfo, 77 GraphLense graphLense, 78 InternalOptions options, 79 CfgPrinter printer, 80 boolean enableDesugaring, 81 boolean enableWholeProgramOptimizations) { 82 assert application != null; 83 assert appInfo != null; 84 assert options != null; 85 this.timing = timing != null ? timing : new Timing("internal"); 86 this.application = application; 87 this.appInfo = appInfo; 88 this.graphLense = graphLense != null ? graphLense : GraphLense.getIdentityLense(); 89 this.options = options; 90 this.printer = printer; 91 Set<DexType> libraryClassesWithOptimizationInfo = markLibraryMethodsReturningReceiver(); 92 this.codeRewriter = new CodeRewriter(appInfo, libraryClassesWithOptimizationInfo); 93 this.lambdaRewriter = enableDesugaring ? new LambdaRewriter(this) : null; 94 this.interfaceMethodRewriter = 95 (enableDesugaring && enableInterfaceMethodDesugaring()) 96 ? new InterfaceMethodRewriter(this) : null; 97 if (enableWholeProgramOptimizations) { 98 assert appInfo.withSubtyping() != null; 99 this.inliner = new Inliner(appInfo.withSubtyping(), graphLense, options); 100 this.outliner = new Outliner(appInfo, options); 101 this.memberValuePropagation = new MemberValuePropagation(appInfo); 102 this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping()); 103 } else { 104 this.inliner = null; 105 this.outliner = null; 106 this.memberValuePropagation = null; 107 this.lensCodeRewriter = null; 108 } 109 } 110 111 /** 112 * Create an IR converter for processing methods with full program optimization disabled. 113 */ 114 public IRConverter( 115 DexApplication application, 116 AppInfo appInfo, 117 InternalOptions options) { 118 this(null, application, appInfo, null, options, null, true, false); 119 } 120 121 /** 122 * Create an IR converter for processing methods without full program optimization enabled. 123 * 124 * The argument <code>enableDesugaring</code> if desugaing is enabled. 125 */ 126 public IRConverter( 127 DexApplication application, 128 AppInfo appInfo, 129 InternalOptions options, 130 boolean enableDesugaring) { 131 this(null, application, appInfo, null, options, null, enableDesugaring, false); 132 } 133 134 /** 135 * Create an IR converter for processing methods with full program optimization disabled. 136 */ 137 public IRConverter( 138 Timing timing, 139 DexApplication application, 140 AppInfo appInfo, 141 InternalOptions options, 142 CfgPrinter printer) { 143 this(timing, application, appInfo, null, options, printer, true, false); 144 } 145 146 /** 147 * Create an IR converter for processing methods with full program optimization enabled. 148 */ 149 public IRConverter( 150 Timing timing, 151 DexApplication application, 152 AppInfoWithSubtyping appInfo, 153 InternalOptions options, 154 CfgPrinter printer, 155 GraphLense graphLense) { 156 this(timing, application, appInfo, graphLense, options, printer, true, true); 157 } 158 159 private boolean enableInterfaceMethodDesugaring() { 160 switch (options.interfaceMethodDesugaring) { 161 case Off: 162 return false; 163 case Auto: 164 return !options.canUseDefaultAndStaticInterfaceMethods(); 165 } 166 throw new Unreachable(); 167 } 168 169 private boolean enableTryWithResourcesDesugaring() { 170 switch (options.tryWithResourcesDesugaring) { 171 case Off: 172 return false; 173 case Auto: 174 return !options.canUseSuppressedExceptions(); 175 } 176 throw new Unreachable(); 177 } 178 179 private Set<DexType> markLibraryMethodsReturningReceiver() { 180 DexItemFactory dexItemFactory = appInfo.dexItemFactory; 181 dexItemFactory.stringBuilderMethods.forEachAppendMethod(this::markReturnsReceiver); 182 dexItemFactory.stringBufferMethods.forEachAppendMethod(this::markReturnsReceiver); 183 return ImmutableSet.of(dexItemFactory.stringBuilderType, dexItemFactory.stringBufferType); 184 } 185 186 private void markReturnsReceiver(DexMethod method) { 187 DexEncodedMethod definition = appInfo.definitionFor(method); 188 if (definition != null) { 189 definition.markReturnsArgument(0); 190 } 191 } 192 193 private void removeLambdaDeserializationMethods() { 194 if (lambdaRewriter != null) { 195 lambdaRewriter.removeLambdaDeserializationMethods(application.classes()); 196 } 197 } 198 199 private void synthesizeLambdaClasses(Builder builder) { 200 if (lambdaRewriter != null) { 201 lambdaRewriter.adjustAccessibility(); 202 lambdaRewriter.synthesizeLambdaClasses(builder); 203 } 204 } 205 206 private void desugarInterfaceMethods( 207 Builder builder, InterfaceMethodRewriter.Flavor includeAllResources) { 208 if (interfaceMethodRewriter != null) { 209 interfaceMethodRewriter.desugarInterfaceMethods(builder, includeAllResources); 210 } 211 } 212 213 public DexApplication convertToDex(ExecutorService executor) throws ExecutionException { 214 removeLambdaDeserializationMethods(); 215 216 convertClassesToDex(application.classes(), executor); 217 218 // Build a new application with jumbo string info, 219 Builder builder = new Builder(application); 220 builder.setHighestSortingString(highestSortingString); 221 222 synthesizeLambdaClasses(builder); 223 desugarInterfaceMethods(builder, ExcludeDexResources); 224 225 return builder.build(); 226 } 227 228 private void convertClassesToDex(Iterable<DexProgramClass> classes, 229 ExecutorService executor) throws ExecutionException { 230 List<Future<?>> futures = new ArrayList<>(); 231 for (DexProgramClass clazz : classes) { 232 futures.add(executor.submit(() -> { 233 convertMethodsToDex(clazz.directMethods()); 234 convertMethodsToDex(clazz.virtualMethods()); 235 })); 236 } 237 ThreadUtils.awaitFutures(futures); 238 } 239 240 private void convertMethodsToDex(DexEncodedMethod[] methods) { 241 for (int i = 0; i < methods.length; i++) { 242 DexEncodedMethod method = methods[i]; 243 if (method.getCode() != null) { 244 boolean matchesMethodFilter = options.methodMatchesFilter(method); 245 if (matchesMethodFilter) { 246 if (method.getCode().isJarCode()) { 247 rewriteCode(method, ignoreOptimizationFeedback, Outliner::noProcessing); 248 } 249 updateHighestSortingStrings(method); 250 } 251 } 252 } 253 } 254 255 public DexApplication optimize() throws ExecutionException { 256 ExecutorService executor = Executors.newSingleThreadExecutor(); 257 try { 258 return optimize(executor); 259 } finally { 260 executor.shutdown(); 261 } 262 } 263 264 public DexApplication optimize(ExecutorService executorService) throws ExecutionException { 265 removeLambdaDeserializationMethods(); 266 267 timing.begin("Build call graph"); 268 callGraph = CallGraph.build(application, appInfo.withSubtyping(), graphLense); 269 timing.end(); 270 271 // The process is in two phases. 272 // 1) Subject all DexEncodedMethods to optimization (except outlining). 273 // - a side effect is candidates for outlining are identified. 274 // 2) Perform outlining for the collected candidates. 275 // Ideally, we should outline eagerly when threshold for a template has been reached. 276 277 // Process the application identifying outlining candidates. 278 timing.begin("IR conversion phase 1"); 279 OptimizationFeedback directFeedback = new OptimizationFeedbackDirect(); 280 while (!callGraph.isEmpty()) { 281 List<DexEncodedMethod> methods = callGraph.extractLeaves(); 282 assert methods.size() > 0; 283 // For testing we have the option to determine the processing order of the methods. 284 if (options.testing.irOrdering != null) { 285 methods = options.testing.irOrdering.apply(methods); 286 } 287 List<Future<?>> futures = new ArrayList<>(); 288 for (DexEncodedMethod method : methods) { 289 futures.add(executorService.submit(() -> { 290 processMethod(method, directFeedback, 291 outliner == null ? Outliner::noProcessing : outliner::identifyCandidates); 292 })); 293 } 294 ThreadUtils.awaitFutures(futures); 295 } 296 timing.end(); 297 298 // Build a new application with jumbo string info. 299 Builder builder = new Builder(application); 300 builder.setHighestSortingString(highestSortingString); 301 302 // Second inlining pass for dealing with double inline callers. 303 if (inliner != null) { 304 inliner.processDoubleInlineCallers(this, ignoreOptimizationFeedback); 305 } 306 307 synthesizeLambdaClasses(builder); 308 desugarInterfaceMethods(builder, IncludeAllResources); 309 310 if (outliner != null) { 311 timing.begin("IR conversion phase 2"); 312 // Compile all classes flagged for outlining and 313 // add the outline support class IF needed. 314 DexProgramClass outlineClass = prepareOutlining(); 315 if (outlineClass != null) { 316 // Process the selected methods for outlining. 317 for (DexEncodedMethod method : outliner.getMethodsSelectedForOutlining()) { 318 // This is the second time we compile this method first mark it not processed. 319 assert !method.getCode().isOutlineCode(); 320 processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate); 321 assert method.isProcessed(); 322 } 323 builder.addSynthesizedClass(outlineClass, true); 324 clearDexMethodCompilationState(outlineClass); 325 } 326 timing.end(); 327 } 328 clearDexMethodCompilationState(); 329 return builder.build(); 330 } 331 332 public void processJumboStrings(DexEncodedMethod method, DexString firstJumboString) { 333 convertMethodJumboStringsOnly(method, firstJumboString); 334 } 335 336 private void clearDexMethodCompilationState() { 337 application.classes().forEach(this::clearDexMethodCompilationState); 338 } 339 340 private void clearDexMethodCompilationState(DexProgramClass clazz) { 341 clazz.forEachMethod(DexEncodedMethod::markNotProcessed); 342 } 343 344 /** 345 * This will replace the Dex code in the method with the Dex code generated from the provided IR. 346 * 347 * This method is *only* intended for testing, where tests manipulate the IR and need runnable Dex 348 * code. 349 * 350 * @param method the method to replace code for 351 * @param code the IR code for the method 352 */ 353 public void replaceCodeForTesting(DexEncodedMethod method, IRCode code) { 354 if (Log.ENABLED) { 355 Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code); 356 } 357 assert code.isConsistentSSA(); 358 RegisterAllocator registerAllocator = performRegisterAllocation(code, method); 359 method.setCode(code, registerAllocator, appInfo.dexItemFactory); 360 if (Log.ENABLED) { 361 Log.debug(getClass(), "Resulting dex code for %s:\n%s", 362 method.toSourceString(), logCode(options, method)); 363 } 364 } 365 366 // Find an unused name for the outlining class. When multiple runs produces additional 367 // outlining the default outlining class might already be present. 368 private DexType computeOutlineClassType() { 369 DexType result; 370 int count = 0; 371 do { 372 String name = options.outline.className + (count == 0 ? "" : Integer.toString(count)); 373 count++; 374 result = application.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name)); 375 } while (application.definitionFor(result) != null); 376 return result; 377 } 378 379 private DexProgramClass prepareOutlining() { 380 if (!outliner.selectMethodsForOutlining()) { 381 return null; 382 } 383 DexProgramClass outlineClass = outliner.buildOutlinerClass(computeOutlineClassType()); 384 optimizeSynthesizedClass(outlineClass); 385 return outlineClass; 386 } 387 388 public void optimizeSynthesizedClass(DexProgramClass clazz) { 389 // Process the generated class, but don't apply any outlining. 390 clazz.forEachMethod(this::optimizeSynthesizedMethod); 391 } 392 393 public void optimizeSynthesizedMethod(DexEncodedMethod method) { 394 // Process the generated method, but don't apply any outlining. 395 processMethod(method, ignoreOptimizationFeedback, Outliner::noProcessing); 396 } 397 398 private String logCode(InternalOptions options, DexEncodedMethod method) { 399 return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString(); 400 } 401 402 public void processMethod(DexEncodedMethod method, 403 OptimizationFeedback feedback, 404 BiConsumer<IRCode, DexEncodedMethod> outlineHandler) { 405 Code code = method.getCode(); 406 boolean matchesMethodFilter = options.methodMatchesFilter(method); 407 if (code != null && matchesMethodFilter) { 408 rewriteCode(method, feedback, outlineHandler); 409 } else { 410 // Mark abstract methods as processed as well. 411 method.markProcessed(Constraint.NEVER); 412 } 413 } 414 415 private void rewriteCode(DexEncodedMethod method, 416 OptimizationFeedback feedback, 417 BiConsumer<IRCode, DexEncodedMethod> outlineHandler) { 418 if (options.verbose) { 419 System.out.println("Processing: " + method.toSourceString()); 420 } 421 if (Log.ENABLED) { 422 Log.debug(getClass(), "Original code for %s:\n%s", 423 method.toSourceString(), logCode(options, method)); 424 } 425 IRCode code = method.buildIR(options); 426 if (code == null) { 427 feedback.markProcessed(method, Constraint.NEVER); 428 return; 429 } 430 if (Log.ENABLED) { 431 Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code); 432 } 433 // Compilation header if printing CFGs for this method. 434 printC1VisualizerHeader(method); 435 printMethod(code, "Initial IR (SSA)"); 436 437 if (lensCodeRewriter != null) { 438 lensCodeRewriter.rewrite(code, method); 439 } else { 440 assert graphLense.isIdentityLense(); 441 } 442 if (memberValuePropagation != null) { 443 memberValuePropagation.rewriteWithConstantValues(code); 444 } 445 if (options.removeSwitchMaps) { 446 // TODO(zerny): Should we support removeSwitchMaps in debug mode? b/62936642 447 assert !options.debug; 448 codeRewriter.removeSwitchMaps(code); 449 } 450 if (options.disableAssertions) { 451 codeRewriter.disableAssertions(code); 452 } 453 if (options.inlineAccessors && inliner != null) { 454 // TODO(zerny): Should we support inlining in debug mode? b/62937285 455 assert !options.debug; 456 inliner.performInlining(method, code, callGraph); 457 } 458 codeRewriter.rewriteLongCompareAndRequireNonNull(code, options); 459 codeRewriter.commonSubexpressionElimination(code); 460 codeRewriter.simplifyArrayConstruction(code); 461 codeRewriter.rewriteMoveResult(code); 462 codeRewriter.splitConstants(code); 463 codeRewriter.foldConstants(code); 464 codeRewriter.rewriteSwitch(code); 465 codeRewriter.simplifyIf(code); 466 if (Log.ENABLED) { 467 Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s", 468 method.toSourceString(), code); 469 } 470 // Dead code removal. Performed after simplifications to remove code that becomes dead 471 // as a result of those simplifications. The following optimizations could reveal more 472 // dead code which is removed right before register allocation in performRegisterAllocation. 473 DeadCodeRemover.removeDeadCode(code, codeRewriter, options); 474 assert code.isConsistentSSA(); 475 476 if (enableTryWithResourcesDesugaring()) { 477 codeRewriter.rewriteThrowableAddAndGetSuppressed(code); 478 } 479 480 if (lambdaRewriter != null) { 481 lambdaRewriter.desugarLambdas(method, code); 482 assert code.isConsistentSSA(); 483 } 484 485 if (interfaceMethodRewriter != null) { 486 interfaceMethodRewriter.rewriteMethodReferences(method, code); 487 assert code.isConsistentSSA(); 488 } 489 490 if (options.outline.enabled) { 491 outlineHandler.accept(code, method); 492 assert code.isConsistentSSA(); 493 } 494 495 codeRewriter.shortenLiveRanges(code); 496 codeRewriter.identifyReturnsArgument(method, code, feedback); 497 498 // Insert code to log arguments if requested. 499 if (options.methodMatchesLogArgumentsFilter(method)) { 500 codeRewriter.logArgumentTypes(method, code); 501 } 502 503 printMethod(code, "Optimized IR (SSA)"); 504 // Perform register allocation. 505 RegisterAllocator registerAllocator = performRegisterAllocation(code, method); 506 method.setCode(code, registerAllocator, appInfo.dexItemFactory); 507 updateHighestSortingStrings(method); 508 if (Log.ENABLED) { 509 Log.debug(getClass(), "Resulting dex code for %s:\n%s", 510 method.toSourceString(), logCode(options, method)); 511 } 512 printMethod(code, "Final IR (non-SSA)"); 513 514 // After all the optimizations have take place, we compute whether method should be inlined. 515 Constraint state; 516 if (!options.inlineAccessors || inliner == null) { 517 state = Constraint.NEVER; 518 } else { 519 state = inliner.identifySimpleMethods(code, method); 520 } 521 feedback.markProcessed(method, state); 522 } 523 524 private synchronized void updateHighestSortingStrings(DexEncodedMethod method) { 525 DexString highestSortingReferencedString = method.getCode().asDexCode().highestSortingString; 526 if (highestSortingReferencedString != null) { 527 if (highestSortingString == null 528 || highestSortingReferencedString.slowCompareTo(highestSortingString) > 0) { 529 highestSortingString = highestSortingReferencedString; 530 } 531 } 532 } 533 534 // Convert a method ensuring that strings sorting equal or higher than the argument 535 // firstJumboString are encoded as jumbo strings. 536 // TODO(sgjesse): Consider replacing this with a direct dex2dex converter instead of going 537 // through IR. 538 private void convertMethodJumboStringsOnly( 539 DexEncodedMethod method, DexString firstJumboString) { 540 // This is only used for methods already converted to Dex, but missing jumbo strings. 541 assert method.getCode() != null && method.getCode().isDexCode(); 542 if (options.verbose) { 543 System.out.println("Processing jumbo strings: " + method.toSourceString()); 544 } 545 if (Log.ENABLED) { 546 Log.debug(getClass(), "Original code for %s:\n%s", 547 method.toSourceString(), logCode(options, method)); 548 } 549 IRCode code = method.buildIR(options); 550 if (Log.ENABLED) { 551 Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", 552 method.toSourceString(), code); 553 } 554 // Compilation header if printing CFGs for this method. 555 printC1VisualizerHeader(method); 556 printMethod(code, "Initial IR (SSA)"); 557 558 // Methods passed through here should have been through IR processing already and 559 // therefore, we skip most of the IR processing. 560 561 // Perform register allocation. 562 RegisterAllocator registerAllocator = performRegisterAllocation(code, method); 563 method.setCode(code, registerAllocator, appInfo.dexItemFactory, firstJumboString); 564 565 if (Log.ENABLED) { 566 Log.debug(getClass(), "Resulting dex code for %s:\n%s", 567 method.toSourceString(), logCode(options, method)); 568 } 569 printMethod(code, "Final IR (non-SSA)"); 570 } 571 572 private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) { 573 // Always perform dead code elimination before register allocation. The register allocator 574 // does not allow dead code (to make sure that we do not waste registers for unneeded values). 575 DeadCodeRemover.removeDeadCode(code, codeRewriter, options); 576 LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options); 577 registerAllocator.allocateRegisters(options.debug); 578 printMethod(code, "After register allocation (non-SSA)"); 579 printLiveRanges(registerAllocator, "Final live ranges."); 580 if (!options.debug) { 581 CodeRewriter.removedUnneededDebugPositions(code); 582 } 583 for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) { 584 CodeRewriter.collapsTrivialGotos(method, code); 585 PeepholeOptimizer.optimize(code, registerAllocator); 586 } 587 CodeRewriter.collapsTrivialGotos(method, code); 588 if (Log.ENABLED) { 589 Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s", 590 method.toSourceString(), code); 591 } 592 return registerAllocator; 593 } 594 595 private void printC1VisualizerHeader(DexEncodedMethod method) { 596 if (printer != null) { 597 printer.begin("compilation"); 598 printer.print("name \"").append(method.toSourceString()).append("\"").ln(); 599 printer.print("method \"").append(method.toSourceString()).append("\"").ln(); 600 printer.print("date 0").ln(); 601 printer.end("compilation"); 602 } 603 } 604 605 private void printMethod(IRCode code, String title) { 606 if (printer != null) { 607 printer.resetUnusedValue(); 608 printer.begin("cfg"); 609 printer.print("name \"").append(title).append("\"\n"); 610 code.print(printer); 611 printer.end("cfg"); 612 } 613 } 614 615 private void printLiveRanges(LinearScanRegisterAllocator allocator, String title) { 616 if (printer != null) { 617 allocator.print(printer, title); 618 } 619 } 620 } 621