001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 /* 019 * $Id: MethodGenerator.java 1225436 2011-12-29 05:09:31Z mrglavas $ 020 */ 021 022 package org.apache.xalan.xsltc.compiler.util; 023 024 import java.util.ArrayList; 025 import java.util.Collections; 026 import java.util.HashMap; 027 import java.util.Hashtable; 028 import java.util.Iterator; 029 import java.util.Map; 030 import java.util.Stack; 031 032 import org.apache.bcel.Constants; 033 import org.apache.bcel.classfile.Field; 034 import org.apache.bcel.classfile.Method; 035 import org.apache.bcel.generic.ALOAD; 036 import org.apache.bcel.generic.ASTORE; 037 import org.apache.bcel.generic.BranchHandle; 038 import org.apache.bcel.generic.BranchInstruction; 039 import org.apache.bcel.generic.ConstantPoolGen; 040 import org.apache.bcel.generic.DLOAD; 041 import org.apache.bcel.generic.DSTORE; 042 import org.apache.bcel.generic.FLOAD; 043 import org.apache.bcel.generic.FSTORE; 044 import org.apache.bcel.generic.GETFIELD; 045 import org.apache.bcel.generic.GOTO; 046 import org.apache.bcel.generic.ICONST; 047 import org.apache.bcel.generic.IfInstruction; 048 import org.apache.bcel.generic.ILOAD; 049 import org.apache.bcel.generic.IndexedInstruction; 050 import org.apache.bcel.generic.INVOKEINTERFACE; 051 import org.apache.bcel.generic.INVOKESPECIAL; 052 import org.apache.bcel.generic.INVOKESTATIC; 053 import org.apache.bcel.generic.INVOKEVIRTUAL; 054 import org.apache.bcel.generic.ISTORE; 055 import org.apache.bcel.generic.Instruction; 056 import org.apache.bcel.generic.InstructionConstants; 057 import org.apache.bcel.generic.InstructionHandle; 058 import org.apache.bcel.generic.InstructionList; 059 import org.apache.bcel.generic.InstructionTargeter; 060 import org.apache.bcel.generic.LocalVariableGen; 061 import org.apache.bcel.generic.LocalVariableInstruction; 062 import org.apache.bcel.generic.LLOAD; 063 import org.apache.bcel.generic.LSTORE; 064 import org.apache.bcel.generic.MethodGen; 065 import org.apache.bcel.generic.NEW; 066 import org.apache.bcel.generic.PUTFIELD; 067 import org.apache.bcel.generic.RET; 068 import org.apache.bcel.generic.Select; 069 import org.apache.bcel.generic.TargetLostException; 070 import org.apache.bcel.generic.Type; 071 072 import org.apache.xalan.xsltc.compiler.Pattern; 073 import org.apache.xalan.xsltc.compiler.XSLTC; 074 075 /** 076 * @author Jacek Ambroziak 077 * @author Santiago Pericas-Geertsen 078 */ 079 public class MethodGenerator extends MethodGen 080 implements org.apache.xalan.xsltc.compiler.Constants { 081 protected static final int INVALID_INDEX = -1; 082 083 private static final String START_ELEMENT_SIG 084 = "(" + STRING_SIG + ")V"; 085 private static final String END_ELEMENT_SIG 086 = START_ELEMENT_SIG; 087 088 private InstructionList _mapTypeSub; 089 090 private static final int DOM_INDEX = 1; 091 private static final int ITERATOR_INDEX = 2; 092 private static final int HANDLER_INDEX = 3; 093 094 private static final int MAX_METHOD_SIZE = 65535; 095 private static final int MAX_BRANCH_TARGET_OFFSET = 32767; 096 private static final int MIN_BRANCH_TARGET_OFFSET = -32768; 097 098 private static final int TARGET_METHOD_SIZE = 60000; 099 private static final int MINIMUM_OUTLINEABLE_CHUNK_SIZE = 1000; 100 101 private Instruction _iloadCurrent; 102 private Instruction _istoreCurrent; 103 private final Instruction _astoreHandler; 104 private final Instruction _aloadHandler; 105 private final Instruction _astoreIterator; 106 private final Instruction _aloadIterator; 107 private final Instruction _aloadDom; 108 private final Instruction _astoreDom; 109 110 private final Instruction _startElement; 111 private final Instruction _endElement; 112 private final Instruction _startDocument; 113 private final Instruction _endDocument; 114 private final Instruction _attribute; 115 private final Instruction _uniqueAttribute; 116 private final Instruction _namespace; 117 118 private final Instruction _setStartNode; 119 private final Instruction _reset; 120 private final Instruction _nextNode; 121 122 private SlotAllocator _slotAllocator; 123 private boolean _allocatorInit = false; 124 private LocalVariableRegistry _localVariableRegistry; 125 126 /** 127 * A mapping between patterns and instruction lists used by 128 * test sequences to avoid compiling the same pattern multiple 129 * times. Note that patterns whose kernels are "*", "node()" 130 * and "@*" can between shared by test sequences. 131 */ 132 private Hashtable _preCompiled = new Hashtable(); 133 134 public MethodGenerator(int access_flags, Type return_type, 135 Type[] arg_types, String[] arg_names, 136 String method_name, String class_name, 137 InstructionList il, ConstantPoolGen cpg) { 138 super(access_flags, return_type, arg_types, arg_names, method_name, 139 class_name, il, cpg); 140 141 _astoreHandler = new ASTORE(HANDLER_INDEX); 142 _aloadHandler = new ALOAD(HANDLER_INDEX); 143 _astoreIterator = new ASTORE(ITERATOR_INDEX); 144 _aloadIterator = new ALOAD(ITERATOR_INDEX); 145 _aloadDom = new ALOAD(DOM_INDEX); 146 _astoreDom = new ASTORE(DOM_INDEX); 147 148 final int startElement = 149 cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE, 150 "startElement", 151 START_ELEMENT_SIG); 152 _startElement = new INVOKEINTERFACE(startElement, 2); 153 154 final int endElement = 155 cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE, 156 "endElement", 157 END_ELEMENT_SIG); 158 _endElement = new INVOKEINTERFACE(endElement, 2); 159 160 final int attribute = 161 cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE, 162 "addAttribute", 163 "(" 164 + STRING_SIG 165 + STRING_SIG 166 + ")V"); 167 _attribute = new INVOKEINTERFACE(attribute, 3); 168 169 final int uniqueAttribute = 170 cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE, 171 "addUniqueAttribute", 172 "(" 173 + STRING_SIG 174 + STRING_SIG 175 + "I)V"); 176 _uniqueAttribute = new INVOKEINTERFACE(uniqueAttribute, 4); 177 178 final int namespace = 179 cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE, 180 "namespaceAfterStartElement", 181 "(" 182 + STRING_SIG 183 + STRING_SIG 184 + ")V"); 185 _namespace = new INVOKEINTERFACE(namespace, 3); 186 187 int index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE, 188 "startDocument", 189 "()V"); 190 _startDocument = new INVOKEINTERFACE(index, 1); 191 192 index = cpg.addInterfaceMethodref(TRANSLET_OUTPUT_INTERFACE, 193 "endDocument", 194 "()V"); 195 _endDocument = new INVOKEINTERFACE(index, 1); 196 197 198 index = cpg.addInterfaceMethodref(NODE_ITERATOR, 199 SET_START_NODE, 200 SET_START_NODE_SIG); 201 _setStartNode = new INVOKEINTERFACE(index, 2); 202 203 index = cpg.addInterfaceMethodref(NODE_ITERATOR, 204 "reset", "()"+NODE_ITERATOR_SIG); 205 _reset = new INVOKEINTERFACE(index, 1); 206 207 index = cpg.addInterfaceMethodref(NODE_ITERATOR, NEXT, NEXT_SIG); 208 _nextNode = new INVOKEINTERFACE(index, 1); 209 210 _slotAllocator = new SlotAllocator(); 211 _slotAllocator.initialize(getLocalVariableRegistry().getLocals(false)); 212 _allocatorInit = true; 213 } 214 215 /** 216 * Allocates a local variable. If the slot allocator has already been 217 * initialized, then call addLocalVariable2() so that the new variable 218 * is known to the allocator. Failing to do this may cause the allocator 219 * to return a slot that is already in use. 220 */ 221 public LocalVariableGen addLocalVariable(String name, Type type, 222 InstructionHandle start, 223 InstructionHandle end) 224 { 225 LocalVariableGen lvg; 226 227 if (_allocatorInit) { 228 lvg = addLocalVariable2(name, type, start); 229 } else { 230 lvg = super.addLocalVariable(name, type, start, end); 231 getLocalVariableRegistry().registerLocalVariable(lvg); 232 } 233 234 return lvg; 235 } 236 237 public LocalVariableGen addLocalVariable2(String name, Type type, 238 InstructionHandle start) 239 { 240 LocalVariableGen lvg = super.addLocalVariable(name, type, 241 _slotAllocator.allocateSlot(type), 242 start, null); 243 getLocalVariableRegistry().registerLocalVariable(lvg); 244 return lvg; 245 } 246 247 private LocalVariableRegistry getLocalVariableRegistry() { 248 if (_localVariableRegistry == null) { 249 _localVariableRegistry = new LocalVariableRegistry(); 250 } 251 252 return _localVariableRegistry; 253 } 254 255 /** 256 * Keeps track of all local variables used in the method. 257 * <p>The 258 * {@link MethodGen#addLocalVariable(String,Type,InstructionHandle,InstructionHandle)}</code> 259 * and 260 * {@link MethodGen#addLocalVariable(String,Type,int,InstructionHandle,InstructionHandle)}</code> 261 * methods of {@link MethodGen} will only keep track of 262 * {@link LocalVariableGen} object until it'ss removed by a call to 263 * {@link MethodGen#removeLocalVariable(LocalVariableGen)}.</p> 264 * <p>In order to support efficient copying of local variables to outlined 265 * methods by 266 * {@link #outline(InstructionHandle,InstructionHandle,String,ClassGenerator)}, 267 * this class keeps track of all local variables defined by the method.</p> 268 */ 269 protected class LocalVariableRegistry { 270 /** 271 * <p>A <code>java.lang.ArrayList</code> of all 272 * {@link LocalVariableGen}s created for this method, indexed by the 273 * slot number of the local variable. The JVM stack frame of local 274 * variables is divided into "slots". A single slot can be used to 275 * store more than one variable in a method, without regard to type, so 276 * long as the byte code keeps the ranges of the two disjoint.</p> 277 * <p>If only one registration of use of a particular slot occurs, the 278 * corresponding entry of <code>_variables</code> contains the 279 * <code>LocalVariableGen</code>; if more than one occurs, the 280 * corresponding entry contains all such <code>LocalVariableGen</code>s 281 * registered for the same slot; and if none occurs, the entry will be 282 * <code>null</code>. 283 */ 284 protected ArrayList _variables = new ArrayList(); 285 286 /** 287 * Maps a name to a {@link LocalVariableGen} 288 */ 289 protected HashMap _nameToLVGMap = new HashMap(); 290 291 /** 292 * Registers a {@link org.apache.bcel.generic.LocalVariableGen} 293 * for this method. 294 * <p><b>Preconditions:</b> 295 * <ul> 296 * <li>The range of instructions for <code>lvg</code> does not 297 * overlap with the range of instructions for any 298 * <code>LocalVariableGen</code> with the same slot index previously 299 * registered for this method. <b><em>(Unchecked.)</em></b></li> 300 * </ul></p> 301 * @param lvg The variable to be registered 302 */ 303 protected void registerLocalVariable(LocalVariableGen lvg) { 304 int slot = lvg.getIndex(); 305 306 int registrySize = _variables.size(); 307 308 // If the LocalVariableGen uses a slot index beyond any previously 309 // encountered, expand the _variables, padding with intervening null 310 // entries as required. 311 if (slot >= registrySize) { 312 for (int i = registrySize; i < slot; i++) { 313 _variables.add(null); 314 } 315 _variables.add(lvg); 316 } else { 317 // If the LocalVariableGen reuses a slot, make sure the entry 318 // in _variables contains an ArrayList and add the newly 319 // registered LocalVariableGen to the list. If the entry in 320 // _variables just contains null padding, store the 321 // LocalVariableGen directly. 322 Object localsInSlot = _variables.get(slot); 323 if (localsInSlot != null) { 324 if (localsInSlot instanceof LocalVariableGen) { 325 ArrayList listOfLocalsInSlot = new ArrayList(); 326 listOfLocalsInSlot.add(localsInSlot); 327 listOfLocalsInSlot.add(lvg); 328 _variables.set(slot, listOfLocalsInSlot); 329 } else { 330 ((ArrayList) localsInSlot).add(lvg); 331 } 332 } else { 333 _variables.set(slot, lvg); 334 } 335 } 336 337 registerByName(lvg); 338 } 339 340 /** 341 * <p>Find which {@link LocalVariableGen}, if any, is registered for a 342 * particular JVM local stack frame slot at a particular position in the 343 * byte code for the method.</p> 344 * <p><b>Preconditions:</b> 345 * <ul> 346 * <li>The {@link InstructionList#setPositions()} has been called for 347 * the {@link InstructionList} associated with this 348 * {@link MethodGenerator}.</li> 349 * </ul></p> 350 * @param slot the JVM local stack frame slot number 351 * @param offset the position in the byte code 352 * @return the <code>LocalVariableGen</code> for the local variable 353 * stored in the relevant slot at the relevant offset; <code>null</code> 354 * if there is none. 355 */ 356 protected LocalVariableGen lookupRegisteredLocalVariable(int slot, 357 int offset) { 358 Object localsInSlot = (_variables != null) ? _variables.get(slot) 359 : null; 360 361 // If this slot index was never used, _variables.get will return 362 // null; if it was used once, it will return the LocalVariableGen; 363 // more than once it will return an ArrayList of all the 364 // LocalVariableGens for variables stored in that slot. For each 365 // LocalVariableGen, check whether its range includes the 366 // specified offset, and return the first such encountered. 367 if (localsInSlot != null) { 368 if (localsInSlot instanceof LocalVariableGen) { 369 LocalVariableGen lvg = (LocalVariableGen)localsInSlot; 370 if (offsetInLocalVariableGenRange(lvg, offset)) { 371 return lvg; 372 } 373 } else { 374 ArrayList listOfLocalsInSlot = (ArrayList) localsInSlot; 375 int size = listOfLocalsInSlot.size(); 376 377 for (int i = 0; i < size; i++) { 378 LocalVariableGen lvg = 379 (LocalVariableGen)listOfLocalsInSlot.get(i); 380 if (offsetInLocalVariableGenRange(lvg, offset)) { 381 return lvg; 382 } 383 } 384 } 385 } 386 387 // No local variable stored in the specified slot at the specified 388 return null; 389 } 390 391 /** 392 * <p>Set up a mapping of the name of the specified 393 * {@link LocalVariableGen} object to the <code>LocalVariableGen</code> 394 * itself.</p> 395 * <p>This is a bit of a hack. XSLTC is relying on the fact that the 396 * name that is being looked up won't be duplicated, which isn't 397 * guaranteed. It replaces code which used to call 398 * {@link MethodGen#getLocalVariables()} and looped through the 399 * <code>LocalVariableGen</code> objects it contained to find the one 400 * with the specified name. However, <code>getLocalVariables()</code> 401 * has the side effect of setting the start and end for any 402 * <code>LocalVariableGen</code> which did not already have them 403 * set, which causes problems for outlining..</p> 404 * <p>See also {@link #lookUpByName(String)} and 405 * {@link #removeByNameTracking(LocalVariableGen)}</P 406 * @param lvg a <code>LocalVariableGen</code> 407 */ 408 protected void registerByName(LocalVariableGen lvg) { 409 Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName()); 410 411 if (duplicateNameEntry == null) { 412 _nameToLVGMap.put(lvg.getName(), lvg); 413 } else { 414 ArrayList sameNameList; 415 416 if (duplicateNameEntry instanceof ArrayList) { 417 sameNameList = (ArrayList) duplicateNameEntry; 418 sameNameList.add(lvg); 419 } else { 420 sameNameList = new ArrayList(); 421 sameNameList.add(duplicateNameEntry); 422 sameNameList.add(lvg); 423 } 424 425 _nameToLVGMap.put(lvg.getName(), sameNameList); 426 } 427 } 428 429 /** 430 * Remove the mapping from the name of the specified 431 * {@link LocalVariableGen} to itself. 432 * See also {@link #registerByName(LocalVariableGen)} and 433 * {@link #lookUpByName(String)} 434 * @param lvg a <code>LocalVariableGen</code> 435 */ 436 protected void removeByNameTracking(LocalVariableGen lvg) { 437 Object duplicateNameEntry = _nameToLVGMap.get(lvg.getName()); 438 439 if (duplicateNameEntry instanceof ArrayList) { 440 ArrayList sameNameList = (ArrayList) duplicateNameEntry; 441 for (int i = 0; i < sameNameList.size(); i++) { 442 if (sameNameList.get(i) == lvg) { 443 sameNameList.remove(i); 444 break; 445 } 446 } 447 } else { 448 _nameToLVGMap.remove(lvg); 449 } 450 } 451 452 /** 453 * <p>Given the name of a variable, finds a {@link LocalVariableGen} 454 * corresponding to it.</p> 455 * <p>See also {@link #registerByName(LocalVariableGen)} and 456 * {@link #removeByNameTracking(LocalVariableGen)}</p> 457 * @param name 458 * @return 459 */ 460 protected LocalVariableGen lookUpByName(String name) { 461 LocalVariableGen lvg = null; 462 Object duplicateNameEntry = _nameToLVGMap.get(name); 463 464 if (duplicateNameEntry instanceof ArrayList) { 465 ArrayList sameNameList = (ArrayList) duplicateNameEntry; 466 467 for (int i = 0; i < sameNameList.size(); i++) { 468 lvg = (LocalVariableGen)sameNameList.get(i); 469 if (lvg.getName() == name) { 470 break; 471 } 472 } 473 } else { 474 lvg = (LocalVariableGen) duplicateNameEntry; 475 } 476 477 return lvg; 478 } 479 480 /** 481 * <p>Gets all {@link LocalVariableGen} objects for this method.</p> 482 * <p>When the <code>includeRemoved</code> argument has the value 483 * <code>false</code>, this method replaces uses of 484 * {@link MethodGen#getLocalVariables()} which has 485 * a side-effect of setting the start and end range for any 486 * <code>LocalVariableGen</code> if either was <code>null</code>. That 487 * side-effect causes problems for outlining of code in XSLTC. 488 * @param includeRemoved Specifies whether all local variables ever 489 * declared should be returned (<code>true</code>) or only those not 490 * removed (<code>false</code>) 491 * @return an array of <code>LocalVariableGen</code> containing all the 492 * local variables 493 */ 494 protected LocalVariableGen[] getLocals(boolean includeRemoved) { 495 LocalVariableGen[] locals = null; 496 ArrayList allVarsEverDeclared = new ArrayList(); 497 498 if (includeRemoved) { 499 int slotCount = allVarsEverDeclared.size(); 500 501 for (int i = 0; i < slotCount; i++) { 502 Object slotEntries = _variables.get(i); 503 if (slotEntries != null) { 504 if (slotEntries instanceof ArrayList) { 505 ArrayList slotList = (ArrayList) slotEntries; 506 507 for (int j = 0; j < slotList.size(); j++) { 508 allVarsEverDeclared.add(slotList.get(i)); 509 } 510 } else { 511 allVarsEverDeclared.add(slotEntries); 512 } 513 } 514 } 515 } else { 516 Iterator nameVarsPairsIter = _nameToLVGMap.entrySet().iterator(); 517 518 while (nameVarsPairsIter.hasNext()) { 519 Map.Entry nameVarsPair = 520 (Map.Entry) nameVarsPairsIter.next(); 521 Object vars = nameVarsPair.getValue(); 522 if (vars != null) { 523 if (vars instanceof ArrayList) { 524 ArrayList varsList = (ArrayList) vars; 525 for (int i = 0; i < varsList.size(); i++) { 526 allVarsEverDeclared.add(varsList.get(i)); 527 } 528 } else { 529 allVarsEverDeclared.add(vars); 530 } 531 } 532 } 533 } 534 535 locals = new LocalVariableGen[allVarsEverDeclared.size()]; 536 allVarsEverDeclared.toArray(locals); 537 538 return locals; 539 } 540 } 541 542 /** 543 * Determines whether a particular variable is in use at a particular offset 544 * in the byte code for this method. 545 * <p><b>Preconditions:</b> 546 * <ul> 547 * <li>The {@link InstructionList#setPositions()} has been called for the 548 * {@link InstructionList} associated with this {@link MethodGenerator}. 549 * </li></ul></p> 550 * @param lvg the {@link LocalVariableGen} for the variable 551 * @param offset the position in the byte code 552 * @return <code>true</code> if and only if the specified variable is in 553 * use at the particular byte code offset. 554 */ 555 boolean offsetInLocalVariableGenRange(LocalVariableGen lvg, int offset) { 556 InstructionHandle lvgStart = lvg.getStart(); 557 InstructionHandle lvgEnd = lvg.getEnd(); 558 559 // If no start handle is recorded for the LocalVariableGen, it is 560 // assumed to be in use from the beginning of the method. 561 if (lvgStart == null) { 562 lvgStart = getInstructionList().getStart(); 563 } 564 565 // If no end handle is recorded for the LocalVariableGen, it is assumed 566 // to be in use to the end of the method. 567 if (lvgEnd == null) { 568 lvgEnd = getInstructionList().getEnd(); 569 } 570 571 // Does the range of the instruction include the specified offset? 572 // Note that the InstructionHandle.getPosition method returns the 573 // offset of the beginning of an instruction. A LocalVariableGen's 574 // range includes the end instruction itself, so that instruction's 575 // length must be taken into consideration in computing whether the 576 // varible is in range at a particular offset. 577 return ((lvgStart.getPosition() <= offset) 578 && (lvgEnd.getPosition() 579 + lvgEnd.getInstruction().getLength() >= offset)); 580 } 581 582 public void removeLocalVariable(LocalVariableGen lvg) { 583 _slotAllocator.releaseSlot(lvg); 584 getLocalVariableRegistry().removeByNameTracking(lvg); 585 super.removeLocalVariable(lvg); 586 } 587 588 public Instruction loadDOM() { 589 return _aloadDom; 590 } 591 592 public Instruction storeDOM() { 593 return _astoreDom; 594 } 595 596 public Instruction storeHandler() { 597 return _astoreHandler; 598 } 599 600 public Instruction loadHandler() { 601 return _aloadHandler; 602 } 603 604 public Instruction storeIterator() { 605 return _astoreIterator; 606 } 607 608 public Instruction loadIterator() { 609 return _aloadIterator; 610 } 611 612 public final Instruction setStartNode() { 613 return _setStartNode; 614 } 615 616 public final Instruction reset() { 617 return _reset; 618 } 619 620 public final Instruction nextNode() { 621 return _nextNode; 622 } 623 624 public final Instruction startElement() { 625 return _startElement; 626 } 627 628 public final Instruction endElement() { 629 return _endElement; 630 } 631 632 public final Instruction startDocument() { 633 return _startDocument; 634 } 635 636 public final Instruction endDocument() { 637 return _endDocument; 638 } 639 640 public final Instruction attribute() { 641 return _attribute; 642 } 643 644 public final Instruction uniqueAttribute() { 645 return _uniqueAttribute; 646 } 647 648 public final Instruction namespace() { 649 return _namespace; 650 } 651 652 public Instruction loadCurrentNode() { 653 if (_iloadCurrent == null) { 654 int idx = getLocalIndex("current"); 655 if (idx > 0) 656 _iloadCurrent = new ILOAD(idx); 657 else 658 _iloadCurrent = new ICONST(0); 659 } 660 return _iloadCurrent; 661 } 662 663 public Instruction storeCurrentNode() { 664 return _istoreCurrent != null 665 ? _istoreCurrent 666 : (_istoreCurrent = new ISTORE(getLocalIndex("current"))); 667 } 668 669 /** by default context node is the same as current node. MK437 */ 670 public Instruction loadContextNode() { 671 return loadCurrentNode(); 672 } 673 674 public Instruction storeContextNode() { 675 return storeCurrentNode(); 676 } 677 678 public int getLocalIndex(String name) { 679 return getLocalVariable(name).getIndex(); 680 } 681 682 public LocalVariableGen getLocalVariable(String name) { 683 return getLocalVariableRegistry().lookUpByName(name); 684 } 685 686 public void setMaxLocals() { 687 688 // Get the current number of local variable slots 689 int maxLocals = super.getMaxLocals(); 690 691 // Get numer of actual variables 692 final LocalVariableGen[] localVars = super.getLocalVariables(); 693 if (localVars != null) { 694 if (localVars.length > maxLocals) 695 maxLocals = localVars.length; 696 } 697 698 // We want at least 5 local variable slots (for parameters) 699 if (maxLocals < 5) maxLocals = 5; 700 701 super.setMaxLocals(maxLocals); 702 } 703 704 /** 705 * Add a pre-compiled pattern to this mode. 706 */ 707 public void addInstructionList(Pattern pattern, InstructionList ilist) { 708 _preCompiled.put(pattern, ilist); 709 } 710 711 /** 712 * Get the instruction list for a pre-compiled pattern. Used by 713 * test sequences to avoid compiling patterns more than once. 714 */ 715 public InstructionList getInstructionList(Pattern pattern) { 716 return (InstructionList) _preCompiled.get(pattern); 717 } 718 719 /** 720 * Used to keep track of an outlineable chunk of instructions in the 721 * current method. See {@link OutlineableChunkStart} and 722 * {@link OutlineableChunkEnd} for more information. 723 */ 724 private static class Chunk implements Comparable { 725 /** 726 * {@link InstructionHandle} of the first instruction in the outlineable 727 * chunk. 728 */ 729 private InstructionHandle m_start; 730 731 /** 732 * {@link org.apache.bcel.generic.InstructionHandle} of the first 733 * instruction in the outlineable chunk. 734 */ 735 private InstructionHandle m_end; 736 737 /** 738 * Number of bytes in the instructions contained in this outlineable 739 * chunk. 740 */ 741 private int m_size; 742 743 /** 744 * <p>Constructor for an outlineable {@link MethodGenerator.Chunk}.</p> 745 * <p><b>Preconditions:</b> 746 * <ul> 747 * <li>The {@link InstructionList#setPositions()} has been called for 748 * the {@link InstructionList} associated with this 749 * {@link MethodGenerator}.</li> 750 * </ul></p> 751 * @param start The {@link InstructionHandle} of the first 752 * instruction in the outlineable chunk. 753 * @param end The {@link InstructionHandle} of the last 754 * instruction in the outlineable chunk. 755 */ 756 Chunk(InstructionHandle start, InstructionHandle end) { 757 m_start = start; 758 m_end = end; 759 m_size = end.getPosition() - start.getPosition(); 760 } 761 762 /** 763 * Determines whether this outlineable {@link MethodGenerator.Chunk} is 764 * followed immediately by the argument 765 * <code>MethodGenerator.Chunk</code>, with no other intervening 766 * instructions, including {@link OutlineableChunkStart} or 767 * {@link OutlineableChunkEnd} instructions. 768 * @param neighbour an outlineable {@link MethodGenerator.Chunk} 769 * @return <code>true</code> if and only if the argument chunk 770 * immediately follows <code>this</code> chunk 771 */ 772 boolean isAdjacentTo(Chunk neighbour) { 773 return getChunkEnd().getNext() == neighbour.getChunkStart(); 774 } 775 776 /** 777 * Getter method for the start of this {@linke MethodGenerator.Chunk} 778 * @return the {@link org.apache.bcel.generic.InstructionHandle} of the 779 * start of this chunk 780 */ 781 InstructionHandle getChunkStart() { 782 return m_start; 783 } 784 785 /** 786 * Getter method for the end of this {@link MethodGenerator.Chunk} 787 * @return the {@link InstructionHandle} of the start of this chunk 788 */ 789 InstructionHandle getChunkEnd() { 790 return m_end; 791 } 792 793 /** 794 * The size of this {@link MethodGenerator.Chunk} 795 * @return the number of bytes in the byte code represented by this 796 * chunk. 797 */ 798 int getChunkSize() { 799 return m_size; 800 } 801 802 /** 803 * Implements the <code>java.util.Comparable.compareTo(Object)</code> 804 * method. 805 * @return 806 * <ul> 807 * <li>A positive <code>int</code> if the length of <code>this</code> 808 * chunk in bytes is greater than that of <code>comparand</code></li> 809 * <li>A negative <code>int</code> if the length of <code>this</code> 810 * chunk in bytes is less than that of <code>comparand</code></li> 811 * <li>Zero, otherwise.</li> 812 * </ul> 813 */ 814 public int compareTo(Object comparand) { 815 return getChunkSize() - ((Chunk)comparand).getChunkSize(); 816 } 817 } 818 819 /** 820 * Find the outlineable chunks in this method that would be the best choices 821 * to outline, based on size and position in the method. 822 * @param classGen The {@link ClassGen} with which the generated methods 823 * will be associated 824 * @param totalMethodSize the size of the bytecode in the original method 825 * @return a <code>java.util.ArrayList</code> containing the 826 * {@link MethodGenerator.Chunk}s that may be outlined from this method 827 */ 828 private ArrayList getCandidateChunks(ClassGenerator classGen, 829 int totalMethodSize) { 830 Iterator instructions = getInstructionList().iterator(); 831 ArrayList candidateChunks = new ArrayList(); 832 ArrayList currLevelChunks = new ArrayList(); 833 Stack subChunkStack = new Stack(); 834 boolean openChunkAtCurrLevel = false; 835 boolean firstInstruction = true; 836 837 InstructionHandle currentHandle; 838 839 if (m_openChunks != 0) { 840 String msg = 841 (new ErrorMsg(ErrorMsg.OUTLINE_ERR_UNBALANCED_MARKERS)) 842 .toString(); 843 throw new InternalError(msg); 844 } 845 846 // Scan instructions in the method, keeping track of the nesting level 847 // of outlineable chunks. 848 // 849 // currLevelChunks 850 // keeps track of the child chunks of a chunk. For each chunk, 851 // there will be a pair of entries: the InstructionHandles for the 852 // start and for the end of the chunk 853 // subChunkStack 854 // a stack containing the partially accumulated currLevelChunks for 855 // each chunk that's still open at the current position in the 856 // InstructionList. 857 // candidateChunks 858 // the list of chunks which have been accepted as candidates chunks 859 // for outlining 860 do { 861 // Get the next instruction. The loop will perform one extra 862 // iteration after it reaches the end of the InstructionList, with 863 // currentHandle set to null. 864 currentHandle = instructions.hasNext() 865 ? (InstructionHandle) instructions.next() 866 : null; 867 Instruction inst = 868 (currentHandle != null) ? currentHandle.getInstruction() 869 : null; 870 871 // At the first iteration, create a chunk representing all the 872 // code in the method. This is done just to simplify the logic - 873 // this chunk can never be outlined because it will be too big. 874 if (firstInstruction) { 875 openChunkAtCurrLevel = true; 876 currLevelChunks.add(currentHandle); 877 firstInstruction = false; 878 } 879 880 // Found a new chunk 881 if (inst instanceof OutlineableChunkStart) { 882 // If last MarkerInstruction encountered was an 883 // OutlineableChunkStart, this represents the first chunk 884 // nested within that previous chunk - push the list of chunks 885 // from the outer level onto the stack 886 if (openChunkAtCurrLevel) { 887 subChunkStack.push(currLevelChunks); 888 currLevelChunks = new ArrayList(); 889 } 890 891 openChunkAtCurrLevel = true; 892 currLevelChunks.add(currentHandle); 893 // Close off an open chunk 894 } else if (currentHandle == null 895 || inst instanceof OutlineableChunkEnd) { 896 ArrayList nestedSubChunks = null; 897 898 // If the last MarkerInstruction encountered was an 899 // OutlineableChunkEnd, it means that the current instruction 900 // marks the end of a chunk that contained child chunks. 901 // Those children might need to be examined below in case they 902 // are better candidates for outlining than the current chunk. 903 if (!openChunkAtCurrLevel) { 904 nestedSubChunks = currLevelChunks; 905 currLevelChunks = (ArrayList)subChunkStack.pop(); 906 } 907 908 // Get the handle for the start of this chunk (the last entry 909 // in currLevelChunks) 910 InstructionHandle chunkStart = 911 (InstructionHandle) currLevelChunks.get( 912 currLevelChunks.size()-1); 913 914 int chunkEndPosition = 915 (currentHandle != null) ? currentHandle.getPosition() 916 : totalMethodSize; 917 int chunkSize = chunkEndPosition - chunkStart.getPosition(); 918 919 // Two ranges of chunk size to consider: 920 // 921 // 1. [0,TARGET_METHOD_SIZE] 922 // Keep this chunk in consideration as a candidate, 923 // and ignore its subchunks, if any - there's nothing to be 924 // gained by outlining both the current chunk and its 925 // children! 926 // 927 // 2. (TARGET_METHOD_SIZE,+infinity) 928 // Ignore this chunk - it's too big. Add its subchunks 929 // as candidates, after merging adjacent chunks to produce 930 // chunks that are as large as possible 931 if (chunkSize <= TARGET_METHOD_SIZE) { 932 currLevelChunks.add(currentHandle); 933 } else { 934 if (!openChunkAtCurrLevel) { 935 int childChunkCount = nestedSubChunks.size() / 2; 936 if (childChunkCount > 0) { 937 Chunk[] childChunks = new Chunk[childChunkCount]; 938 939 // Gather all the child chunks of the current chunk 940 for (int i = 0; i < childChunkCount; i++) { 941 InstructionHandle start = 942 (InstructionHandle) nestedSubChunks 943 .get(i*2); 944 InstructionHandle end = 945 (InstructionHandle) nestedSubChunks 946 .get(i*2+1); 947 948 childChunks[i] = new Chunk(start, end); 949 } 950 951 // Merge adjacent siblings 952 ArrayList mergedChildChunks = 953 mergeAdjacentChunks(childChunks); 954 955 // Add chunks that mean minimum size requirements 956 // to the list of candidate chunks for outlining 957 for (int i = 0; i < mergedChildChunks.size(); i++) { 958 Chunk mergedChunk = 959 (Chunk)mergedChildChunks.get(i); 960 int mergedSize = mergedChunk.getChunkSize(); 961 962 if (mergedSize >= MINIMUM_OUTLINEABLE_CHUNK_SIZE 963 && mergedSize <= TARGET_METHOD_SIZE) { 964 candidateChunks.add(mergedChunk); 965 } 966 } 967 } 968 } 969 970 // Drop the chunk which was too big 971 currLevelChunks.remove(currLevelChunks.size() - 1); 972 } 973 974 // currLevelChunks contains pairs of InstructionHandles. If 975 // its size is an odd number, the loop has encountered the 976 // start of a chunk at this level, but not its end. 977 openChunkAtCurrLevel = ((currLevelChunks.size() & 0x1) == 1); 978 } 979 980 } while (currentHandle != null); 981 982 return candidateChunks; 983 } 984 985 /** 986 * Merge adjacent sibling chunks to produce larger candidate chunks for 987 * outlining 988 * @param chunks array of sibling {@link MethodGenerator.Chunk}s that are 989 * under consideration for outlining. Chunks must be in 990 * the order encountered in the {@link InstructionList} 991 * @return a <code>java.util.ArrayList</code> of 992 * <code>MethodGenerator.Chunk</code>s maximally merged 993 */ 994 private ArrayList mergeAdjacentChunks(Chunk[] chunks) { 995 int[] adjacencyRunStart = new int[chunks.length]; 996 int[] adjacencyRunLength = new int[chunks.length]; 997 boolean[] chunkWasMerged = new boolean[chunks.length]; 998 999 int maximumRunOfChunks = 0; 1000 int startOfCurrentRun; 1001 int numAdjacentRuns = 0; 1002 1003 ArrayList mergedChunks = new ArrayList(); 1004 1005 startOfCurrentRun = 0; 1006 1007 // Loop through chunks, and record in adjacencyRunStart where each 1008 // run of adjacent chunks begins and how many are in that run. For 1009 // example, given chunks A B C D E F, if A is adjacent to B, but not 1010 // to C, and C, D, E and F are all adjacent, 1011 // adjacencyRunStart[0] == 0; adjacencyRunLength[0] == 2 1012 // adjacencyRunStart[1] == 2; adjacencyRunLength[1] == 4 1013 for (int i = 1; i < chunks.length; i++) { 1014 if (!chunks[i-1].isAdjacentTo(chunks[i])) { 1015 int lengthOfRun = i - startOfCurrentRun; 1016 1017 // Track the longest run of chunks found 1018 if (maximumRunOfChunks < lengthOfRun) { 1019 maximumRunOfChunks = lengthOfRun; 1020 } 1021 1022 if (lengthOfRun > 1 ) { 1023 adjacencyRunLength[numAdjacentRuns] = lengthOfRun; 1024 adjacencyRunStart[numAdjacentRuns] = startOfCurrentRun; 1025 numAdjacentRuns++; 1026 } 1027 1028 startOfCurrentRun = i; 1029 } 1030 } 1031 1032 if (chunks.length - startOfCurrentRun > 1) { 1033 int lengthOfRun = chunks.length - startOfCurrentRun; 1034 1035 // Track the longest run of chunks found 1036 if (maximumRunOfChunks < lengthOfRun) { 1037 maximumRunOfChunks = lengthOfRun; 1038 } 1039 1040 adjacencyRunLength[numAdjacentRuns] = 1041 chunks.length - startOfCurrentRun; 1042 adjacencyRunStart[numAdjacentRuns] = startOfCurrentRun; 1043 numAdjacentRuns++; 1044 } 1045 1046 // Try merging adjacent chunks to come up with better sized chunks for 1047 // outlining. This algorithm is not optimal, but it should be 1048 // reasonably fast. Consider an example like this, where four chunks 1049 // of the sizes specified in brackets are adjacent. The best way of 1050 // combining these chunks would be to merge the first pair and merge 1051 // the last three to form two chunks, but the algorithm will merge the 1052 // three in the middle instead, leaving three chunks in all. 1053 // [25000] [25000] [20000] [1000] [20000] 1054 1055 // Start by trying to merge the maximum number of adjacent chunks, and 1056 // work down from there. 1057 for (int numToMerge = maximumRunOfChunks; numToMerge>1; numToMerge--) { 1058 // Look at each run of adjacent chunks 1059 for (int run = 0; run < numAdjacentRuns; run++) { 1060 int runStart = adjacencyRunStart[run]; 1061 int runEnd = runStart + adjacencyRunLength[run] - 1; 1062 1063 boolean foundChunksToMerge = false; 1064 1065 // Within the current run of adjacent chunks, look at all 1066 // "subruns" of length numToMerge, until we run out or find 1067 // a subrun that can be merged. 1068 for (int mergeStart = runStart; 1069 mergeStart+numToMerge-1 <= runEnd && !foundChunksToMerge; 1070 mergeStart++) { 1071 int mergeEnd = mergeStart + numToMerge - 1; 1072 int mergeSize = 0; 1073 1074 // Find out how big the subrun is 1075 for (int j = mergeStart; j <= mergeEnd; j++) { 1076 mergeSize = mergeSize + chunks[j].getChunkSize(); 1077 } 1078 1079 // If the current subrun is small enough to outline, 1080 // merge it, and split the remaining chunks in the run 1081 if (mergeSize <= TARGET_METHOD_SIZE) { 1082 foundChunksToMerge = true; 1083 1084 for (int j = mergeStart; j <= mergeEnd; j++) { 1085 chunkWasMerged[j] = true; 1086 } 1087 1088 mergedChunks.add( 1089 new Chunk(chunks[mergeStart].getChunkStart(), 1090 chunks[mergeEnd].getChunkEnd())); 1091 1092 // Adjust the length of the current run of adjacent 1093 // chunks to end at the newly merged chunk... 1094 adjacencyRunLength[run] = 1095 adjacencyRunStart[run] - mergeStart; 1096 1097 int trailingRunLength = runEnd - mergeEnd; 1098 1099 // and any chunks that follow the newly merged chunk 1100 // in the current run of adjacent chunks form another 1101 // new run of adjacent chunks 1102 if (trailingRunLength >= 2) { 1103 adjacencyRunStart[numAdjacentRuns] = mergeEnd + 1; 1104 adjacencyRunLength[numAdjacentRuns] = 1105 trailingRunLength; 1106 numAdjacentRuns++; 1107 } 1108 } 1109 } 1110 } 1111 } 1112 1113 // Make a final pass for any chunk that wasn't merged with a sibling 1114 // and include it in the list of chunks after merging. 1115 for (int i = 0; i < chunks.length; i++) { 1116 if (!chunkWasMerged[i]) { 1117 mergedChunks.add(chunks[i]); 1118 } 1119 } 1120 1121 return mergedChunks; 1122 } 1123 1124 /** 1125 * Breaks up the IL for this {@link MethodGenerator} into separate 1126 * outlined methods so that no method exceeds the 64KB limit on the length 1127 * of the byte code associated with a method. 1128 * @param classGen The {@link ClassGen} with which the generated methods 1129 * will be associated 1130 * @param originalMethodSize The number of bytes of bytecode represented by 1131 * the {@link InstructionList} of this method 1132 * @return an array of the outlined <code>Method</code>s and the original 1133 * method itself 1134 */ 1135 public Method[] outlineChunks(ClassGenerator classGen, 1136 int originalMethodSize) { 1137 ArrayList methodsOutlined = new ArrayList(); 1138 int currentMethodSize = originalMethodSize; 1139 1140 int outlinedCount = 0; 1141 boolean moreMethodsOutlined; 1142 String originalMethodName = getName(); 1143 1144 // Special handling for initialization methods. No other methods can 1145 // include the less than and greater than characters in their names, 1146 // so we munge the names here. 1147 if (originalMethodName.equals("<init>")) { 1148 originalMethodName = "$lt$init$gt$"; 1149 } else if (originalMethodName.equals("<clinit>")) { 1150 originalMethodName = "$lt$clinit$gt$"; 1151 } 1152 1153 // Loop until the original method comes in under the JVM limit or 1154 // the loop was unable to outline any more methods 1155 do { 1156 // Get all the best candidates for outlining, and sort them in 1157 // ascending order of size 1158 ArrayList candidateChunks = getCandidateChunks(classGen, 1159 currentMethodSize); 1160 Collections.sort(candidateChunks); 1161 1162 moreMethodsOutlined = false; 1163 1164 // Loop over the candidates for outlining, from the largest to the 1165 // smallest and outline them one at a time, until the loop has 1166 // outlined all or the original method comes in under the JVM 1167 // limit on the size of a method. 1168 for (int i = candidateChunks.size()-1; 1169 i >= 0 && currentMethodSize > TARGET_METHOD_SIZE; 1170 i--) { 1171 Chunk chunkToOutline = (Chunk)candidateChunks.get(i); 1172 1173 methodsOutlined.add(outline(chunkToOutline.getChunkStart(), 1174 chunkToOutline.getChunkEnd(), 1175 originalMethodName + "$outline$" 1176 + outlinedCount, 1177 classGen)); 1178 outlinedCount++; 1179 moreMethodsOutlined = true; 1180 1181 InstructionList il = getInstructionList(); 1182 InstructionHandle lastInst = il.getEnd(); 1183 il.setPositions(); 1184 1185 // Check the size of the method now 1186 currentMethodSize = 1187 lastInst.getPosition() 1188 + lastInst.getInstruction().getLength(); 1189 } 1190 } while (moreMethodsOutlined && currentMethodSize > TARGET_METHOD_SIZE); 1191 1192 // Outlining failed to reduce the size of the current method 1193 // sufficiently. Throw an internal error. 1194 if (currentMethodSize > MAX_METHOD_SIZE) { 1195 String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_METHOD_TOO_BIG)) 1196 .toString(); 1197 throw new InternalError(msg); 1198 } 1199 1200 Method[] methodsArr = new Method[methodsOutlined.size() + 1]; 1201 methodsOutlined.toArray(methodsArr); 1202 1203 methodsArr[methodsOutlined.size()] = getThisMethod(); 1204 1205 return methodsArr; 1206 } 1207 1208 /** 1209 * Given an outlineable chunk of code in the current {@link MethodGenerator} 1210 * move ("outline") the chunk to a new method, and replace the chunk in the 1211 * old method with a reference to that new method. No 1212 * {@link OutlineableChunkStart} or {@link OutlineableChunkEnd} instructions 1213 * are copied. 1214 * @param first The {@link InstructionHandle} of the first instruction in 1215 * the chunk to outline 1216 * @param last The <code>InstructionHandle</code> of the last instruction in 1217 * the chunk to outline 1218 * @param outlinedMethodName The name of the new method 1219 * @param classGen The {@link ClassGenerator} of which the original 1220 * and new methods will be members 1221 * @return The new {@link Method} containing the outlined code. 1222 */ 1223 private Method outline(InstructionHandle first, InstructionHandle last, 1224 String outlinedMethodName, ClassGenerator classGen) { 1225 // We're not equipped to deal with exception handlers yet. Bail out! 1226 if (getExceptionHandlers().length != 0) { 1227 String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_TRY_CATCH)) 1228 .toString(); 1229 throw new InternalError(msg); 1230 } 1231 1232 int outlineChunkStartOffset = first.getPosition(); 1233 int outlineChunkEndOffset = last.getPosition() 1234 + last.getInstruction().getLength(); 1235 1236 ConstantPoolGen cpg = getConstantPool(); 1237 1238 // Create new outlined method with signature: 1239 // 1240 // private final outlinedMethodName(CopyLocals copyLocals); 1241 // 1242 // CopyLocals is an object that is used to copy-in/copy-out local 1243 // variables that are used by the outlined method. Only locals whose 1244 // value is potentially set or referenced outside the range of the 1245 // chunk that is being outlined will be represented in CopyLocals. The 1246 // type of the variable for copying local variables is actually 1247 // generated to be unique - it is not named CopyLocals. 1248 // 1249 // The outlined method never needs to be referenced outside of this 1250 // class, and will never be overridden, so we mark it private final. 1251 final InstructionList newIL = new InstructionList(); 1252 1253 final XSLTC xsltc = classGen.getParser().getXSLTC(); 1254 final String argTypeName = xsltc.getHelperClassName(); 1255 final Type[] argTypes = 1256 new Type[] {(new ObjectType(argTypeName)).toJCType()}; 1257 final String argName = "copyLocals"; 1258 final String[] argNames = new String[] {argName}; 1259 1260 int methodAttributes = ACC_PRIVATE | ACC_FINAL; 1261 final boolean isStaticMethod = (getAccessFlags() & ACC_STATIC) != 0; 1262 1263 if (isStaticMethod) { 1264 methodAttributes = methodAttributes | ACC_STATIC; 1265 } 1266 1267 final MethodGenerator outlinedMethodGen = 1268 new MethodGenerator(methodAttributes, 1269 org.apache.bcel.generic.Type.VOID, 1270 argTypes, argNames, outlinedMethodName, 1271 getClassName(), newIL, cpg); 1272 1273 // Create class for copying local variables to the outlined method. 1274 // The fields the class will need to contain will be determined as the 1275 // code in the outlineable chunk is examined. 1276 ClassGenerator copyAreaCG 1277 = new ClassGenerator(argTypeName, OBJECT_CLASS, argTypeName+".java", 1278 ACC_FINAL | ACC_PUBLIC | ACC_SUPER, null, 1279 classGen.getStylesheet()) { 1280 public boolean isExternal() { 1281 return true; 1282 } 1283 }; 1284 ConstantPoolGen copyAreaCPG = copyAreaCG.getConstantPool(); 1285 copyAreaCG.addEmptyConstructor(ACC_PUBLIC); 1286 1287 // Number of fields in the copy class 1288 int copyAreaFieldCount = 0; 1289 1290 // The handle for the instruction after the last one to be outlined. 1291 // Note that this should never end up being null. An outlineable chunk 1292 // won't contain a RETURN instruction or other branch out of the chunk, 1293 // and the JVM specification prohibits code in a method from just 1294 // "falling off the end" so this should always point to a valid handle. 1295 InstructionHandle limit = last.getNext(); 1296 1297 // InstructionLists for copying values into and out of an instance of 1298 // CopyLocals: 1299 // oldMethCoypInIL - from locals in old method into an instance 1300 // of the CopyLocals class (oldMethCopyInIL) 1301 // oldMethCopyOutIL - from CopyLocals back into locals in the old 1302 // method 1303 // newMethCopyInIL - from CopyLocals into locals in the new 1304 // method 1305 // newMethCopyOutIL - from locals in new method into the instance 1306 // of the CopyLocals class 1307 InstructionList oldMethCopyInIL = new InstructionList(); 1308 InstructionList oldMethCopyOutIL = new InstructionList(); 1309 InstructionList newMethCopyInIL = new InstructionList(); 1310 InstructionList newMethCopyOutIL = new InstructionList(); 1311 1312 // Allocate instance of class in which we'll copy in or copy out locals 1313 // and make two copies: last copy is used to invoke constructor; 1314 // other two are used for references to fields in the CopyLocals object 1315 InstructionHandle outlinedMethodCallSetup = 1316 oldMethCopyInIL.append(new NEW(cpg.addClass(argTypeName))); 1317 oldMethCopyInIL.append(InstructionConstants.DUP); 1318 oldMethCopyInIL.append(InstructionConstants.DUP); 1319 oldMethCopyInIL.append( 1320 new INVOKESPECIAL(cpg.addMethodref(argTypeName, "<init>", "()V"))); 1321 1322 // Generate code to invoke the new outlined method, and place the code 1323 // on oldMethCopyOutIL 1324 InstructionHandle outlinedMethodRef; 1325 1326 if (isStaticMethod) { 1327 outlinedMethodRef = 1328 oldMethCopyOutIL.append( 1329 new INVOKESTATIC(cpg.addMethodref( 1330 classGen.getClassName(), 1331 outlinedMethodName, 1332 outlinedMethodGen.getSignature()))); 1333 } else { 1334 oldMethCopyOutIL.append(InstructionConstants.THIS); 1335 oldMethCopyOutIL.append(InstructionConstants.SWAP); 1336 outlinedMethodRef = 1337 oldMethCopyOutIL.append( 1338 new INVOKEVIRTUAL(cpg.addMethodref( 1339 classGen.getClassName(), 1340 outlinedMethodName, 1341 outlinedMethodGen.getSignature()))); 1342 } 1343 1344 // Used to keep track of the first in a sequence of 1345 // OutlineableChunkStart instructions 1346 boolean chunkStartTargetMappingsPending = false; 1347 InstructionHandle pendingTargetMappingHandle = null; 1348 1349 // Used to keep track of the last instruction that was copied 1350 InstructionHandle lastCopyHandle = null; 1351 1352 // Keeps track of the mapping from instruction handles in the old 1353 // method to instruction handles in the outlined method. Only need 1354 // to track instructions that are targeted by something else in the 1355 // generated BCEL 1356 HashMap targetMap = new HashMap(); 1357 1358 // Keeps track of the mapping from local variables in the old method 1359 // to local variables in the outlined method. 1360 HashMap localVarMap = new HashMap(); 1361 1362 HashMap revisedLocalVarStart = new HashMap(); 1363 HashMap revisedLocalVarEnd = new HashMap(); 1364 1365 // Pass 1: Make copies of all instructions, append them to the new list 1366 // and associate old instruction references with the new ones, i.e., 1367 // a 1:1 mapping. The special marker instructions are not copied. 1368 // Also, identify local variables whose values need to be copied into or 1369 // out of the new outlined method, and builds up targetMap and 1370 // localVarMap as described above. The code identifies those local 1371 // variables first so that they can have fixed slots in the stack 1372 // frame for the outlined method assigned them ahead of all those 1373 // variables that don't need to exist for the entirety of the outlined 1374 // method invocation. 1375 for (InstructionHandle ih = first; ih != limit; ih = ih.getNext()) { 1376 Instruction inst = ih.getInstruction(); 1377 1378 // MarkerInstructions are not copied, so if something else targets 1379 // one, the targetMap will point to the nearest copied sibling 1380 // InstructionHandle: for an OutlineableChunkEnd, the nearest 1381 // preceding sibling; for an OutlineableChunkStart, the nearest 1382 // following sibling. 1383 if (inst instanceof MarkerInstruction) { 1384 if (ih.hasTargeters()) { 1385 if (inst instanceof OutlineableChunkEnd) { 1386 targetMap.put(ih, lastCopyHandle); 1387 } else { 1388 if (!chunkStartTargetMappingsPending) { 1389 chunkStartTargetMappingsPending = true; 1390 pendingTargetMappingHandle = ih; 1391 } 1392 } 1393 } 1394 } else { 1395 // Copy the instruction and append it to the outlined method's 1396 // InstructionList. 1397 Instruction c = inst.copy(); // Use clone for shallow copy 1398 1399 if (c instanceof BranchInstruction) { 1400 lastCopyHandle = newIL.append((BranchInstruction)c); 1401 } else { 1402 lastCopyHandle = newIL.append(c); 1403 } 1404 1405 if (c instanceof LocalVariableInstruction 1406 || c instanceof RET) { 1407 // For any instruction that touches a local variable, 1408 // check whether the local variable's value needs to be 1409 // copied into or out of the outlined method. If so, 1410 // generate the code to perform the necessary copying, and 1411 // use localVarMap to map the variable in the original 1412 // method to the variable in the new method. 1413 IndexedInstruction lvi = (IndexedInstruction)c; 1414 int oldLocalVarIndex = lvi.getIndex(); 1415 LocalVariableGen oldLVG = 1416 getLocalVariableRegistry() 1417 .lookupRegisteredLocalVariable(oldLocalVarIndex, 1418 ih.getPosition()); 1419 LocalVariableGen newLVG = 1420 (LocalVariableGen)localVarMap.get(oldLVG); 1421 1422 // Has the code already mapped this local variable to a 1423 // local in the new method? 1424 if (localVarMap.get(oldLVG) == null) { 1425 // Determine whether the local variable needs to be 1426 // copied into or out of the outlined by checking 1427 // whether the range of instructions in which the 1428 // variable is accessible is outside the range of 1429 // instructions in the outlineable chunk. 1430 // Special case a chunk start offset of zero: a local 1431 // variable live at that position must be a method 1432 // parameter, so the code doesn't need to check whether 1433 // the variable is live before that point; being live 1434 // at offset zero is sufficient to know that the value 1435 // must be copied in to the outlined method. 1436 boolean copyInLocalValue = 1437 offsetInLocalVariableGenRange(oldLVG, 1438 (outlineChunkStartOffset != 0) 1439 ? outlineChunkStartOffset-1 1440 : 0); 1441 boolean copyOutLocalValue = 1442 offsetInLocalVariableGenRange(oldLVG, 1443 outlineChunkEndOffset+1); 1444 1445 // For any variable that needs to be copied into or out 1446 // of the outlined method, create a field in the 1447 // CopyLocals class, and generate the necessary code for 1448 // copying the value. 1449 if (copyInLocalValue || copyOutLocalValue) { 1450 String varName = oldLVG.getName(); 1451 Type varType = oldLVG.getType(); 1452 newLVG = outlinedMethodGen.addLocalVariable(varName, 1453 varType, 1454 null, 1455 null); 1456 int newLocalVarIndex = newLVG.getIndex(); 1457 String varSignature = varType.getSignature(); 1458 1459 // Record the mapping from the old local to the new 1460 localVarMap.put(oldLVG, newLVG); 1461 1462 copyAreaFieldCount++; 1463 String copyAreaFieldName = 1464 "field" + copyAreaFieldCount; 1465 copyAreaCG.addField( 1466 new Field(ACC_PUBLIC, 1467 copyAreaCPG.addUtf8(copyAreaFieldName), 1468 copyAreaCPG.addUtf8(varSignature), 1469 null, copyAreaCPG.getConstantPool())); 1470 1471 int fieldRef = cpg.addFieldref(argTypeName, 1472 copyAreaFieldName, 1473 varSignature); 1474 1475 if (copyInLocalValue) { 1476 // Generate code for the old method to store the 1477 // value of the local into the correct field in 1478 // CopyLocals prior to invocation of the 1479 // outlined method. 1480 oldMethCopyInIL.append( 1481 InstructionConstants.DUP); 1482 InstructionHandle copyInLoad = 1483 oldMethCopyInIL.append( 1484 loadLocal(oldLocalVarIndex, varType)); 1485 oldMethCopyInIL.append(new PUTFIELD(fieldRef)); 1486 1487 // If the end of the live range of the old 1488 // variable was in the middle of the outlined 1489 // chunk. Make the load of its value the new 1490 // end of its range. 1491 if (!copyOutLocalValue) { 1492 revisedLocalVarEnd.put(oldLVG, copyInLoad); 1493 } 1494 1495 // Generate code for start of the outlined 1496 // method to copy the value from a field in 1497 // CopyLocals to the new local in the outlined 1498 // method 1499 newMethCopyInIL.append( 1500 InstructionConstants.ALOAD_1); 1501 newMethCopyInIL.append(new GETFIELD(fieldRef)); 1502 newMethCopyInIL.append( 1503 storeLocal(newLocalVarIndex, varType)); 1504 } 1505 1506 if (copyOutLocalValue) { 1507 // Generate code for the end of the outlined 1508 // method to copy the value from the new local 1509 // variable into a field in CopyLocals 1510 // method 1511 newMethCopyOutIL.append( 1512 InstructionConstants.ALOAD_1); 1513 newMethCopyOutIL.append( 1514 loadLocal(newLocalVarIndex, varType)); 1515 newMethCopyOutIL.append(new PUTFIELD(fieldRef)); 1516 1517 // Generate code to copy the value from a field 1518 // in CopyLocals into a local in the original 1519 // method following invocation of the outlined 1520 // method. 1521 oldMethCopyOutIL.append( 1522 InstructionConstants.DUP); 1523 oldMethCopyOutIL.append(new GETFIELD(fieldRef)); 1524 InstructionHandle copyOutStore = 1525 oldMethCopyOutIL.append( 1526 storeLocal(oldLocalVarIndex, varType)); 1527 1528 // If the start of the live range of the old 1529 // variable was in the middle of the outlined 1530 // chunk. Make this store into it the new start 1531 // of its range. 1532 if (!copyInLocalValue) { 1533 revisedLocalVarStart.put(oldLVG, 1534 copyOutStore); 1535 } 1536 } 1537 } 1538 } 1539 } 1540 1541 if (ih.hasTargeters()) { 1542 targetMap.put(ih, lastCopyHandle); 1543 } 1544 1545 // If this is the first instruction copied following a sequence 1546 // of OutlineableChunkStart instructions, indicate that the 1547 // sequence of old instruction all map to this newly created 1548 // instruction 1549 if (chunkStartTargetMappingsPending) { 1550 do { 1551 targetMap.put(pendingTargetMappingHandle, 1552 lastCopyHandle); 1553 pendingTargetMappingHandle = 1554 pendingTargetMappingHandle.getNext(); 1555 } while(pendingTargetMappingHandle != ih); 1556 1557 chunkStartTargetMappingsPending = false; 1558 } 1559 } 1560 } 1561 1562 // Pass 2: Walk old and new instruction lists, updating branch targets 1563 // and local variable references in the new list 1564 InstructionHandle ih = first; 1565 InstructionHandle ch = newIL.getStart(); 1566 1567 while (ch != null) { 1568 // i == old instruction; c == copied instruction 1569 Instruction i = ih.getInstruction(); 1570 Instruction c = ch.getInstruction(); 1571 1572 if (i instanceof BranchInstruction) { 1573 BranchInstruction bc = (BranchInstruction)c; 1574 BranchInstruction bi = (BranchInstruction)i; 1575 InstructionHandle itarget = bi.getTarget(); // old target 1576 1577 // New target must be in targetMap 1578 InstructionHandle newTarget = 1579 (InstructionHandle)targetMap.get(itarget); 1580 1581 bc.setTarget(newTarget); 1582 1583 // Handle LOOKUPSWITCH or TABLESWITCH which may have many 1584 // target instructions 1585 if (bi instanceof Select) { 1586 InstructionHandle[] itargets = ((Select)bi).getTargets(); 1587 InstructionHandle[] ctargets = ((Select)bc).getTargets(); 1588 1589 // Update all targets 1590 for (int j=0; j < itargets.length; j++) { 1591 ctargets[j] = 1592 (InstructionHandle)targetMap.get(itargets[j]); 1593 } 1594 } 1595 } else if (i instanceof LocalVariableInstruction 1596 || i instanceof RET) { 1597 // For any instruction that touches a local variable, 1598 // map the location of the variable in the original 1599 // method to its location in the new method. 1600 IndexedInstruction lvi = (IndexedInstruction)c; 1601 int oldLocalVarIndex = lvi.getIndex(); 1602 LocalVariableGen oldLVG = 1603 getLocalVariableRegistry() 1604 .lookupRegisteredLocalVariable(oldLocalVarIndex, 1605 ih.getPosition()); 1606 LocalVariableGen newLVG = 1607 (LocalVariableGen)localVarMap.get(oldLVG); 1608 int newLocalVarIndex; 1609 1610 if (newLVG == null) { 1611 // Create new variable based on old variable - use same 1612 // name and type, but we will let the variable be active 1613 // for the entire outlined method. 1614 // LocalVariableGen oldLocal = oldLocals[oldLocalVarIndex]; 1615 String varName = oldLVG.getName(); 1616 Type varType = oldLVG.getType(); 1617 newLVG = outlinedMethodGen.addLocalVariable(varName, 1618 varType, 1619 null, 1620 null); 1621 newLocalVarIndex = newLVG.getIndex(); 1622 localVarMap.put(oldLVG, newLVG); 1623 1624 // The old variable's live range was wholly contained in 1625 // the outlined chunk. There should no longer be stores 1626 // of values into it or loads of its value, so we can just 1627 // mark its live range as the reference to the outlined 1628 // method. 1629 revisedLocalVarStart.put(oldLVG, outlinedMethodRef); 1630 revisedLocalVarEnd.put(oldLVG, outlinedMethodRef); 1631 } else { 1632 newLocalVarIndex = newLVG.getIndex(); 1633 } 1634 lvi.setIndex(newLocalVarIndex); 1635 } 1636 1637 // If the old instruction marks the end of the range of a local 1638 // variable, make sure that any slots on the stack reserved for 1639 // local variables are made available for reuse by calling 1640 // MethodGenerator.removeLocalVariable 1641 if (ih.hasTargeters()) { 1642 InstructionTargeter[] targeters = ih.getTargeters(); 1643 1644 for (int idx = 0; idx < targeters.length; idx++) { 1645 InstructionTargeter targeter = targeters[idx]; 1646 1647 if (targeter instanceof LocalVariableGen 1648 && ((LocalVariableGen)targeter).getEnd()==ih) { 1649 Object newLVG = localVarMap.get(targeter); 1650 if (newLVG != null) { 1651 outlinedMethodGen.removeLocalVariable( 1652 (LocalVariableGen)newLVG); 1653 } 1654 } 1655 } 1656 } 1657 1658 // If the current instruction in the original list was a marker, 1659 // it wasn't copied, so don't advance through the list of copied 1660 // instructions yet. 1661 if (!(i instanceof MarkerInstruction)) { 1662 ch = ch.getNext(); 1663 } 1664 ih = ih.getNext(); 1665 1666 } 1667 1668 // POP the reference to the CopyLocals object from the stack 1669 oldMethCopyOutIL.append(InstructionConstants.POP); 1670 1671 // Now that the generation of the outlined code is complete, update 1672 // the old local variables with new start and end ranges, as required. 1673 Iterator revisedLocalVarStartPairIter = revisedLocalVarStart.entrySet() 1674 .iterator(); 1675 while (revisedLocalVarStartPairIter.hasNext()) { 1676 Map.Entry lvgRangeStartPair = 1677 (Map.Entry)revisedLocalVarStartPairIter.next(); 1678 LocalVariableGen lvg = (LocalVariableGen)lvgRangeStartPair.getKey(); 1679 InstructionHandle startInst = 1680 (InstructionHandle)lvgRangeStartPair.getValue(); 1681 1682 lvg.setStart(startInst); 1683 1684 } 1685 1686 Iterator revisedLocalVarEndPairIter = revisedLocalVarEnd.entrySet() 1687 .iterator(); 1688 while (revisedLocalVarEndPairIter.hasNext()) { 1689 Map.Entry lvgRangeEndPair = 1690 (Map.Entry)revisedLocalVarEndPairIter.next(); 1691 LocalVariableGen lvg = (LocalVariableGen)lvgRangeEndPair.getKey(); 1692 InstructionHandle endInst = 1693 (InstructionHandle)lvgRangeEndPair.getValue(); 1694 1695 lvg.setEnd(endInst); 1696 } 1697 1698 xsltc.dumpClass(copyAreaCG.getJavaClass()); 1699 1700 // Assemble the instruction lists so that the old method invokes the 1701 // new outlined method 1702 InstructionList oldMethodIL = getInstructionList(); 1703 1704 oldMethodIL.insert(first, oldMethCopyInIL); 1705 oldMethodIL.insert(first, oldMethCopyOutIL); 1706 1707 // Insert the copying code into the outlined method 1708 newIL.insert(newMethCopyInIL); 1709 newIL.append(newMethCopyOutIL); 1710 newIL.append(InstructionConstants.RETURN); 1711 1712 // Discard instructions in outlineable chunk from old method 1713 try { 1714 oldMethodIL.delete(first, last); 1715 } catch (TargetLostException e) { 1716 InstructionHandle[] targets = e.getTargets(); 1717 // If there were still references to old instructions lingering, 1718 // clean those up. The only instructions targetting the deleted 1719 // instructions should have been part of the chunk that was just 1720 // deleted, except that instructions might branch to the start of 1721 // the outlined chunk; similarly, all the live ranges of local 1722 // variables should have been adjusted, except for unreferenced 1723 // variables. 1724 for (int i = 0; i < targets.length; i++) { 1725 InstructionHandle lostTarget = targets[i]; 1726 InstructionTargeter[] targeters = lostTarget.getTargeters(); 1727 for (int j = 0; j < targeters.length; j++) { 1728 if (targeters[j] instanceof LocalVariableGen) { 1729 LocalVariableGen lvgTargeter = 1730 (LocalVariableGen) targeters[j]; 1731 // In the case of any lingering variable references, 1732 // just make the live range point to the outlined 1733 // function reference. Such variables should be unused 1734 // anyway. 1735 if (lvgTargeter.getStart() == lostTarget) { 1736 lvgTargeter.setStart(outlinedMethodRef); 1737 } 1738 if (lvgTargeter.getEnd() == lostTarget) { 1739 lvgTargeter.setEnd(outlinedMethodRef); 1740 } 1741 } else { 1742 targeters[j].updateTarget(lostTarget, 1743 outlinedMethodCallSetup); 1744 } 1745 } 1746 } 1747 } 1748 1749 // Make a copy for the new method of all exceptions that might be thrown 1750 String[] exceptions = getExceptions(); 1751 for (int i = 0; i < exceptions.length; i++) { 1752 outlinedMethodGen.addException(exceptions[i]); 1753 } 1754 1755 return outlinedMethodGen.getThisMethod(); 1756 } 1757 1758 /** 1759 * Helper method to generate an instance of a subclass of 1760 * {@link LoadInstruction} based on the specified {@link Type} that will 1761 * load the specified local variable 1762 * @param index the JVM stack frame index of the variable that is to be 1763 * loaded 1764 * @param type the {@link Type} of the variable 1765 * @return the generated {@link LoadInstruction} 1766 */ 1767 private static Instruction loadLocal(int index, Type type) { 1768 if (type == Type.BOOLEAN) { 1769 return new ILOAD(index); 1770 } else if (type == Type.INT) { 1771 return new ILOAD(index); 1772 } else if (type == Type.SHORT) { 1773 return new ILOAD(index); 1774 } else if (type == Type.LONG) { 1775 return new LLOAD(index); 1776 } else if (type == Type.BYTE) { 1777 return new ILOAD(index); 1778 } else if (type == Type.CHAR) { 1779 return new ILOAD(index); 1780 } else if (type == Type.FLOAT) { 1781 return new FLOAD(index); 1782 } else if (type == Type.DOUBLE) { 1783 return new DLOAD(index); 1784 } else { 1785 return new ALOAD(index); 1786 } 1787 } 1788 1789 /** 1790 * Helper method to generate an instance of a subclass of 1791 * {@link StoreInstruction} based on the specified {@link Type} that will 1792 * store a value in the specified local variable 1793 * @param index the JVM stack frame index of the variable that is to be 1794 * stored 1795 * @param type the {@link Type} of the variable 1796 * @return the generated {@link StoredInstruction} 1797 */ 1798 private static Instruction storeLocal(int index, Type type) { 1799 if (type == Type.BOOLEAN) { 1800 return new ISTORE(index); 1801 } else if (type == Type.INT) { 1802 return new ISTORE(index); 1803 } else if (type == Type.SHORT) { 1804 return new ISTORE(index); 1805 } else if (type == Type.LONG) { 1806 return new LSTORE(index); 1807 } else if (type == Type.BYTE) { 1808 return new ISTORE(index); 1809 } else if (type == Type.CHAR) { 1810 return new ISTORE(index); 1811 } else if (type == Type.FLOAT) { 1812 return new FSTORE(index); 1813 } else if (type == Type.DOUBLE) { 1814 return new DSTORE(index); 1815 } else { 1816 return new ASTORE(index); 1817 } 1818 } 1819 1820 /** 1821 * Track the number of outlineable chunks seen. 1822 */ 1823 private int m_totalChunks = 0; 1824 1825 /** 1826 * Track the number of outlineable chunks started but not yet ended. Used 1827 * to detect imbalances in byte code generation. 1828 */ 1829 private int m_openChunks = 0; 1830 1831 /** 1832 * Mark the end of the method's 1833 * {@link InstructionList} as the start of an outlineable chunk of code. 1834 * The outlineable chunk begins after the {@link InstructionHandle} that is 1835 * at the end of the method's {@link InstructionList}, or at the start of 1836 * the method if the <code>InstructionList</code> is empty. 1837 * See {@link OutlineableChunkStart} for more information. 1838 */ 1839 public void markChunkStart() { 1840 // m_chunkTree.markChunkStart(); 1841 getInstructionList() 1842 .append(OutlineableChunkStart.OUTLINEABLECHUNKSTART); 1843 m_totalChunks++; 1844 m_openChunks++; 1845 } 1846 1847 /** 1848 * Mark the end of an outlineable chunk of code. See 1849 * {@link OutlineableChunkStart} for more information. 1850 */ 1851 public void markChunkEnd() { 1852 // m_chunkTree.markChunkEnd(); 1853 getInstructionList() 1854 .append(OutlineableChunkEnd.OUTLINEABLECHUNKEND); 1855 m_openChunks--; 1856 if (m_openChunks < 0) { 1857 String msg = (new ErrorMsg(ErrorMsg.OUTLINE_ERR_UNBALANCED_MARKERS)) 1858 .toString(); 1859 throw new InternalError(msg); 1860 } 1861 } 1862 1863 /** 1864 * <p>Get all {@link Method}s generated by this {@link MethodGenerator}. 1865 * The {@link MethodGen#getMethod()} only returns a single 1866 * <code>Method</code> object. This method takes into account the Java 1867 * Virtual Machine Specification limit of 64KB on the size of a method, and 1868 * may return more than one <code>Method</code>.</p> 1869 * <p>If the code associated with the <code>MethodGenerator</code> would 1870 * exceed the 64KB limit, this method will attempt to split the code in 1871 * the {@link InstructionList} associated with this 1872 * <code>MethodGenerator</code> into several methods.</p> 1873 * @param classGen the {@link ClassGenerator} of which these methods are 1874 * members 1875 * @return an array of all the <code>Method</code>s generated 1876 */ 1877 Method[] getGeneratedMethods(ClassGenerator classGen) { 1878 Method[] generatedMethods; 1879 InstructionList il = getInstructionList(); 1880 InstructionHandle last = il.getEnd(); 1881 1882 il.setPositions(); 1883 1884 int instructionListSize = 1885 last.getPosition() + last.getInstruction().getLength(); 1886 1887 // Need to look for any branch target offsets that exceed the range 1888 // [-32768,32767] 1889 if (instructionListSize > MAX_BRANCH_TARGET_OFFSET) { 1890 boolean ilChanged = widenConditionalBranchTargetOffsets(); 1891 1892 // If any branch instructions needed widening, recompute the size 1893 // of the byte code for the method 1894 if (ilChanged) { 1895 il.setPositions(); 1896 last = il.getEnd(); 1897 instructionListSize = 1898 last.getPosition() + last.getInstruction().getLength(); 1899 } 1900 } 1901 1902 if (instructionListSize > MAX_METHOD_SIZE) { 1903 generatedMethods = outlineChunks(classGen, instructionListSize); 1904 } else { 1905 generatedMethods = new Method[] {getThisMethod()}; 1906 } 1907 return generatedMethods; 1908 } 1909 1910 protected Method getThisMethod() { 1911 stripAttributes(true); 1912 setMaxLocals(); 1913 setMaxStack(); 1914 removeNOPs(); 1915 1916 return getMethod(); 1917 } 1918 /** 1919 * <p>Rewrites branches to avoid the JVM limits of relative branch 1920 * offsets. There is no need to invoke this method if the bytecode for the 1921 * {@link MethodGenerator} does not exceed 32KB.</p> 1922 * <p>The Java Virtual Machine Specification permits the code portion of a 1923 * method to be up to 64KB in length. However, some control transfer 1924 * instructions specify relative offsets as a signed 16-bit quantity, 1925 * limiting the range to a subset of the instructions that might be in a 1926 * method.</p> 1927 * <p>The <code>TABLESWITCH</code> and <code>LOOKUPSWITCH</code> 1928 * instructions always use 32-bit signed relative offsets, so they are 1929 * immune to this problem.</p> 1930 * <p>The <code>GOTO</code> and <code>JSR</code> 1931 * instructions come in two forms, one of which uses 16-bit relative 1932 * offsets, and the other of which uses 32-bit relative offsets. The BCEL 1933 * library decides whether to use the wide form of <code>GOTO</code> or 1934 * <code>JSR</code>instructions based on the relative offset of the target 1935 * of the instruction without any intervention by the user of the 1936 * library.</p> 1937 * <p>This leaves the various conditional branch instructions, 1938 * <code>IFEQ</code>, <code>IFNULL</code>, <code>IF_ICMPEQ</code>, 1939 * <em>et al.</em>, all of which use 16-bit signed relative offsets, with no 1940 * 32-bit wide form available.</p> 1941 * <p>This method scans the {@link InstructionList} associated with this 1942 * {@link MethodGenerator} and finds all conditional branch instructions 1943 * that might exceed the 16-bit limitation for relative branch offsets. 1944 * The logic of each such instruction is inverted, and made to target the 1945 * instruction which follows it. An unconditional branch to the original 1946 * target of the instruction is then inserted between the conditional 1947 * branch and the instruction which previously followed it. The 1948 * unconditional branch is permitted to have a 16-bit or a 32-bit relative 1949 * offset, as described above. For example, 1950 * <code> 1951 * 1234: NOP 1952 * ... 1953 * 55278: IFEQ -54044 1954 * 55280: NOP 1955 * </code> 1956 * is rewritten as 1957 * <code> 1958 * 1234: NOP 1959 * ... 1960 * 55278: IFNE 7 1961 * 55280: GOTO_W -54046 1962 * 55285: NOP 1963 * </code></p> 1964 * <p><b>Preconditions:</b> 1965 * <ul><li>The {@link InstructionList#setPositions()} has been called for 1966 * the <code>InstructionList</code> associated with this 1967 * <code>MethodGenerator</code>. 1968 * </li></ul></p> 1969 * <p><b>Postconditions:</b> 1970 * <ul><li>Any further changes to the <code>InstructionList</code> for this 1971 * <code>MethodGenerator</code> will invalidate the changes made by this 1972 * method.</li></ul> 1973 * </p> 1974 * @return <code>true</code> if the <code>InstructionList</code> was 1975 * modified; <code>false</code> otherwise 1976 * @see The Java Virtual Machine Specification, Second Edition 1977 */ 1978 boolean widenConditionalBranchTargetOffsets() { 1979 boolean ilChanged = false; 1980 int maxOffsetChange = 0; 1981 InstructionList il = getInstructionList(); 1982 1983 // Loop through all the instructions, finding those that would be 1984 // affected by inserting new instructions in the InstructionList, and 1985 // calculating the maximum amount by which the relative offset between 1986 // two instructions could possibly change. 1987 // In part this loop duplicates code in 1988 // org.apache.bcel.generic.InstructionList.setPosition(), which does 1989 // this to determine whether to use 16-bit or 32-bit offsets for GOTO 1990 // and JSR instructions. Ideally, that method would do the same for 1991 // conditional branch instructions, but it doesn't, so we duplicate the 1992 // processing here. 1993 for (InstructionHandle ih = il.getStart(); 1994 ih != null; 1995 ih = ih.getNext()) { 1996 Instruction inst = ih.getInstruction(); 1997 1998 switch (inst.getOpcode()) { 1999 // Instructions that may have 16-bit or 32-bit branch targets. 2000 // The size of the branch offset might increase by two bytes. 2001 case Constants.GOTO: 2002 case Constants.JSR: 2003 maxOffsetChange = maxOffsetChange + 2; 2004 break; 2005 // Instructions that contain padding for alignment purposes 2006 // Up to three bytes of padding might be needed. For greater 2007 // accuracy, we should be able to discount any padding already 2008 // added to these instructions by InstructionList.setPosition(), 2009 // their APIs do not expose that information. 2010 case Constants.TABLESWITCH: 2011 case Constants.LOOKUPSWITCH: 2012 maxOffsetChange = maxOffsetChange + 3; 2013 break; 2014 // Instructions that might be rewritten by this method as a 2015 // conditional branch followed by an unconditional branch. 2016 // The unconditional branch would require five bytes. 2017 case Constants.IF_ACMPEQ: 2018 case Constants.IF_ACMPNE: 2019 case Constants.IF_ICMPEQ: 2020 case Constants.IF_ICMPGE: 2021 case Constants.IF_ICMPGT: 2022 case Constants.IF_ICMPLE: 2023 case Constants.IF_ICMPLT: 2024 case Constants.IF_ICMPNE: 2025 case Constants.IFEQ: 2026 case Constants.IFGE: 2027 case Constants.IFGT: 2028 case Constants.IFLE: 2029 case Constants.IFLT: 2030 case Constants.IFNE: 2031 case Constants.IFNONNULL: 2032 case Constants.IFNULL: 2033 maxOffsetChange = maxOffsetChange + 5; 2034 break; 2035 } 2036 } 2037 2038 // Now that the maximum number of bytes by which the method might grow 2039 // has been determined, look for conditional branches to see which 2040 // might possibly exceed the 16-bit relative offset. 2041 for (InstructionHandle ih = il.getStart(); 2042 ih != null; 2043 ih = ih.getNext()) { 2044 Instruction inst = ih.getInstruction(); 2045 2046 if (inst instanceof IfInstruction) { 2047 IfInstruction oldIfInst = (IfInstruction)inst; 2048 BranchHandle oldIfHandle = (BranchHandle)ih; 2049 InstructionHandle target = oldIfInst.getTarget(); 2050 int relativeTargetOffset = target.getPosition() 2051 - oldIfHandle.getPosition(); 2052 2053 // Consider the worst case scenario in which the conditional 2054 // branch and its target are separated by all the instructions 2055 // in the method that might increase in size. If that results 2056 // in a relative offset that cannot be represented as a 32-bit 2057 // signed quantity, rewrite the instruction as described above. 2058 if ((relativeTargetOffset - maxOffsetChange 2059 < MIN_BRANCH_TARGET_OFFSET) 2060 || (relativeTargetOffset + maxOffsetChange 2061 > MAX_BRANCH_TARGET_OFFSET)) { 2062 // Invert the logic of the IF instruction, and append 2063 // that to the InstructionList following the original IF 2064 // instruction 2065 InstructionHandle nextHandle = oldIfHandle.getNext(); 2066 IfInstruction invertedIfInst = oldIfInst.negate(); 2067 BranchHandle invertedIfHandle = il.append(oldIfHandle, 2068 invertedIfInst); 2069 2070 // Append an unconditional branch to the target of the 2071 // original IF instruction after the new IF instruction 2072 BranchHandle gotoHandle = il.append(invertedIfHandle, 2073 new GOTO(target)); 2074 2075 // If the original IF was the last instruction in 2076 // InstructionList, add a new no-op to act as the target 2077 // of the new IF 2078 if (nextHandle == null) { 2079 nextHandle = il.append(gotoHandle, NOP); 2080 } 2081 2082 // Make the new IF instruction branch around the GOTO 2083 invertedIfHandle.updateTarget(target, nextHandle); 2084 2085 // If anything still "points" to the old IF instruction, 2086 // make adjustments to refer to either the new IF or GOTO 2087 // instruction 2088 if (oldIfHandle.hasTargeters()) { 2089 InstructionTargeter[] targeters = 2090 oldIfHandle.getTargeters(); 2091 2092 for (int i = 0; i < targeters.length; i++) { 2093 InstructionTargeter targeter = targeters[i]; 2094 // Ideally, one should simply be able to use 2095 // InstructionTargeter.updateTarget to change 2096 // references to the old IF instruction to the new 2097 // IF instruction. However, if a LocalVariableGen 2098 // indicated the old IF marked the end of the range 2099 // in which the IF variable is in use, the live 2100 // range of the variable must extend to include the 2101 // newly created GOTO instruction. The need for 2102 // this sort of specific knowledge of an 2103 // implementor of the InstructionTargeter interface 2104 // makes the code more fragile. Future implementors 2105 // of the interface might have similar requirements 2106 // which wouldn't be accommodated seemlessly. 2107 if (targeter instanceof LocalVariableGen) { 2108 LocalVariableGen lvg = 2109 (LocalVariableGen) targeter; 2110 if (lvg.getStart() == oldIfHandle) { 2111 lvg.setStart(invertedIfHandle); 2112 } else if (lvg.getEnd() == oldIfHandle) { 2113 lvg.setEnd(gotoHandle); 2114 } 2115 } else { 2116 targeter.updateTarget(oldIfHandle, 2117 invertedIfHandle); 2118 } 2119 } 2120 } 2121 2122 try { 2123 il.delete(oldIfHandle); 2124 } catch (TargetLostException tle) { 2125 // This can never happen - we updated the list of 2126 // instructions that target the deleted instruction 2127 // prior to deleting it. 2128 String msg = 2129 new ErrorMsg(ErrorMsg.OUTLINE_ERR_DELETED_TARGET, 2130 tle.getMessage()).toString(); 2131 throw new InternalError(msg); 2132 } 2133 2134 // Adjust the pointer in the InstructionList to point after 2135 // the newly inserted IF instruction 2136 ih = gotoHandle; 2137 2138 // Indicate that this method rewrote at least one IF 2139 ilChanged = true; 2140 } 2141 } 2142 } 2143 2144 // Did this method rewrite any IF instructions? 2145 return ilChanged; 2146 } 2147 }