/* * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.java.util.jar.pack; import com.sun.java.util.jar.pack.Package.Class; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; import static com.sun.java.util.jar.pack.Constants.*; /** * Represents a chunk of bytecodes. * @author John Rose */ class Code extends Attribute.Holder { Class.Method m; public Code(Class.Method m) { this.m = m; } public Class.Method getMethod() { return m; } public Class thisClass() { return m.thisClass(); } public Package getPackage() { return m.thisClass().getPackage(); } public ConstantPool.Entry[] getCPMap() { return m.getCPMap(); } private static final ConstantPool.Entry[] noRefs = ConstantPool.noRefs; // The following fields are used directly by the ClassReader, etc. int max_stack; int max_locals; ConstantPool.Entry handler_class[] = noRefs; int handler_start[] = noInts; int handler_end[] = noInts; int handler_catch[] = noInts; byte[] bytes; Fixups fixups; // reference relocations, if any are required Object insnMap; // array of instruction boundaries int getLength() { return bytes.length; } int getMaxStack() { return max_stack; } void setMaxStack(int ms) { max_stack = ms; } int getMaxNALocals() { int argsize = m.getArgumentSize(); return max_locals - argsize; } void setMaxNALocals(int ml) { int argsize = m.getArgumentSize(); max_locals = argsize + ml; } int getHandlerCount() { assert(handler_class.length == handler_start.length); assert(handler_class.length == handler_end.length); assert(handler_class.length == handler_catch.length); return handler_class.length; } void setHandlerCount(int h) { if (h > 0) { handler_class = new ConstantPool.Entry[h]; handler_start = new int[h]; handler_end = new int[h]; handler_catch = new int[h]; // caller must fill these in ASAP } } void setBytes(byte[] bytes) { this.bytes = bytes; if (fixups != null) fixups.setBytes(bytes); } void setInstructionMap(int[] insnMap, int mapLen) { //int[] oldMap = null; //assert((oldMap = getInstructionMap()) != null); this.insnMap = allocateInstructionMap(insnMap, mapLen); //assert(Arrays.equals(oldMap, getInstructionMap())); } void setInstructionMap(int[] insnMap) { setInstructionMap(insnMap, insnMap.length); } int[] getInstructionMap() { return expandInstructionMap(getInsnMap()); } void addFixups(Collection<Fixups.Fixup> moreFixups) { if (fixups == null) { fixups = new Fixups(bytes); } assert(fixups.getBytes() == bytes); fixups.addAll(moreFixups); } public void trimToSize() { if (fixups != null) { fixups.trimToSize(); if (fixups.size() == 0) fixups = null; } super.trimToSize(); } protected void visitRefs(int mode, Collection<ConstantPool.Entry> refs) { int verbose = getPackage().verbose; if (verbose > 2) System.out.println("Reference scan "+this); refs.addAll(Arrays.asList(handler_class)); if (fixups != null) { fixups.visitRefs(refs); } else { // References (to a local cpMap) are embedded in the bytes. ConstantPool.Entry[] cpMap = getCPMap(); for (Instruction i = instructionAt(0); i != null; i = i.next()) { if (verbose > 4) System.out.println(i); int cpref = i.getCPIndex(); if (cpref >= 0) { refs.add(cpMap[cpref]); } } } // Handle attribute list: super.visitRefs(mode, refs); } // Since bytecodes are the single largest contributor to // package size, it's worth a little bit of trouble // to reduce the per-bytecode memory footprint. // In the current scheme, half of the bulk of these arrays // due to bytes, and half to shorts. (Ints are insignificant.) // Given an average of 1.8 bytes per instruction, this means // instruction boundary arrays are about a 75% overhead--tolerable. // (By using bytes, we get 33% savings over just shorts and ints. // Using both bytes and shorts gives 66% savings over just ints.) static final boolean shrinkMaps = true; private Object allocateInstructionMap(int[] insnMap, int mapLen) { int PClimit = getLength(); if (shrinkMaps && PClimit <= Byte.MAX_VALUE - Byte.MIN_VALUE) { byte[] map = new byte[mapLen+1]; for (int i = 0; i < mapLen; i++) { map[i] = (byte)(insnMap[i] + Byte.MIN_VALUE); } map[mapLen] = (byte)(PClimit + Byte.MIN_VALUE); return map; } else if (shrinkMaps && PClimit < Short.MAX_VALUE - Short.MIN_VALUE) { short[] map = new short[mapLen+1]; for (int i = 0; i < mapLen; i++) { map[i] = (short)(insnMap[i] + Short.MIN_VALUE); } map[mapLen] = (short)(PClimit + Short.MIN_VALUE); return map; } else { int[] map = Arrays.copyOf(insnMap, mapLen + 1); map[mapLen] = PClimit; return map; } } private int[] expandInstructionMap(Object map0) { int[] imap; if (map0 instanceof byte[]) { byte[] map = (byte[]) map0; imap = new int[map.length-1]; for (int i = 0; i < imap.length; i++) { imap[i] = map[i] - Byte.MIN_VALUE; } } else if (map0 instanceof short[]) { short[] map = (short[]) map0; imap = new int[map.length-1]; for (int i = 0; i < imap.length; i++) { imap[i] = map[i] - Byte.MIN_VALUE; } } else { int[] map = (int[]) map0; imap = Arrays.copyOfRange(map, 0, map.length - 1); } return imap; } Object getInsnMap() { // Build a map of instruction boundaries. if (insnMap != null) { return insnMap; } int[] map = new int[getLength()]; int fillp = 0; for (Instruction i = instructionAt(0); i != null; i = i.next()) { map[fillp++] = i.getPC(); } // Make it byte[], short[], or int[] according to the max BCI. insnMap = allocateInstructionMap(map, fillp); //assert(assertBCICodingsOK()); return insnMap; } /** Encode the given BCI as an instruction boundary number. * For completeness, irregular (non-boundary) BCIs are * encoded compactly immediately after the boundary numbers. * This encoding is the identity mapping outside 0..length, * and it is 1-1 everywhere. All by itself this technique * improved zipped rt.jar compression by 2.6%. */ public int encodeBCI(int bci) { if (bci <= 0 || bci > getLength()) return bci; Object map0 = getInsnMap(); int i, len; if (shrinkMaps && map0 instanceof byte[]) { byte[] map = (byte[]) map0; len = map.length; i = Arrays.binarySearch(map, (byte)(bci + Byte.MIN_VALUE)); } else if (shrinkMaps && map0 instanceof short[]) { short[] map = (short[]) map0; len = map.length; i = Arrays.binarySearch(map, (short)(bci + Short.MIN_VALUE)); } else { int[] map = (int[]) map0; len = map.length; i = Arrays.binarySearch(map, bci); } assert(i != -1); assert(i != 0); assert(i != len); assert(i != -len-1); return (i >= 0) ? i : len + bci - (-i-1); } public int decodeBCI(int bciCode) { if (bciCode <= 0 || bciCode > getLength()) return bciCode; Object map0 = getInsnMap(); int i, len; // len == map.length // If bciCode < len, result is map[bciCode], the common and fast case. // Otherwise, let map[i] be the smallest map[*] larger than bci. // Then, required by the return statement of encodeBCI: // bciCode == len + bci - i // Thus: // bci-i == bciCode-len // map[i]-adj-i == bciCode-len ; adj in (0..map[i]-map[i-1]) // We can solve this by searching for adjacent entries // map[i-1], map[i] such that: // map[i-1]-(i-1) <= bciCode-len < map[i]-i // This can be approximated by searching map[i] for bciCode and then // linear searching backward. Given the right i, we then have: // bci == bciCode-len + i // This linear search is at its worst case for indexes in the beginning // of a large method, but it's not clear that this is a problem in // practice, since BCIs are usually on instruction boundaries. if (shrinkMaps && map0 instanceof byte[]) { byte[] map = (byte[]) map0; len = map.length; if (bciCode < len) return map[bciCode] - Byte.MIN_VALUE; i = Arrays.binarySearch(map, (byte)(bciCode + Byte.MIN_VALUE)); if (i < 0) i = -i-1; int key = bciCode-len + Byte.MIN_VALUE; for (;; i--) { if (map[i-1]-(i-1) <= key) break; } } else if (shrinkMaps && map0 instanceof short[]) { short[] map = (short[]) map0; len = map.length; if (bciCode < len) return map[bciCode] - Short.MIN_VALUE; i = Arrays.binarySearch(map, (short)(bciCode + Short.MIN_VALUE)); if (i < 0) i = -i-1; int key = bciCode-len + Short.MIN_VALUE; for (;; i--) { if (map[i-1]-(i-1) <= key) break; } } else { int[] map = (int[]) map0; len = map.length; if (bciCode < len) return map[bciCode]; i = Arrays.binarySearch(map, bciCode); if (i < 0) i = -i-1; int key = bciCode-len; for (;; i--) { if (map[i-1]-(i-1) <= key) break; } } return bciCode-len + i; } public void finishRefs(ConstantPool.Index ix) { if (fixups != null) { fixups.finishRefs(ix); fixups = null; } // Code attributes are finished in ClassWriter.writeAttributes. } Instruction instructionAt(int pc) { return Instruction.at(bytes, pc); } static boolean flagsRequireCode(int flags) { // A method's flags force it to have a Code attribute, // if the flags are neither native nor abstract. return (flags & (Modifier.NATIVE | Modifier.ABSTRACT)) == 0; } public String toString() { return m+".Code"; } /// Fetching values from my own array. public int getInt(int pc) { return Instruction.getInt(bytes, pc); } public int getShort(int pc) { return Instruction.getShort(bytes, pc); } public int getByte(int pc) { return Instruction.getByte(bytes, pc); } void setInt(int pc, int x) { Instruction.setInt(bytes, pc, x); } void setShort(int pc, int x) { Instruction.setShort(bytes, pc, x); } void setByte(int pc, int x) { Instruction.setByte(bytes, pc, x); } /* TEST CODE ONLY private boolean assertBCICodingsOK() { boolean ok = true; int len = java.lang.reflect.Array.getLength(insnMap); int base = 0; if (insnMap.getClass().getComponentType() == Byte.TYPE) base = Byte.MIN_VALUE; if (insnMap.getClass().getComponentType() == Short.TYPE) base = Short.MIN_VALUE; for (int i = -1, imax = getLength()+1; i <= imax; i++) { int bci = i; int enc = Math.min(-999, bci-1); int dec = enc; try { enc = encodeBCI(bci); dec = decodeBCI(enc); } catch (RuntimeException ee) { ee.printStackTrace(); } if (dec == bci) { //System.out.println("BCI="+bci+(enc<len?"":" ")+" enc="+enc); continue; } if (ok) { for (int q = 0; q <= 1; q++) { StringBuffer sb = new StringBuffer(); sb.append("bci "+(q==0?"map":"del")+"["+len+"] = {"); for (int j = 0; j < len; j++) { int mapi = ((Number)java.lang.reflect.Array.get(insnMap, j)).intValue() - base; mapi -= j*q; sb.append(" "+mapi); } sb.append(" }"); System.out.println("*** "+sb); } } System.out.println("*** BCI="+bci+" enc="+enc+" dec="+dec); ok = false; } return ok; } //*/ }