/* * Copyright (c) 2001, 2012, 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.ConstantPool.*; import com.sun.java.util.jar.pack.Package.Class; import com.sun.java.util.jar.pack.Package.File; import com.sun.java.util.jar.pack.Package.InnerClass; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static com.sun.java.util.jar.pack.Constants.*; /** * Writer for a package file. * @author John Rose */ class PackageWriter extends BandStructure { Package pkg; OutputStream finalOut; Package.Version packageVersion; PackageWriter(Package pkg, OutputStream out) throws IOException { this.pkg = pkg; this.finalOut = out; // Caller has specified maximum class file version in the package: initHighestClassVersion(pkg.getHighestClassVersion()); } void write() throws IOException { boolean ok = false; try { if (verbose > 0) { Utils.log.info("Setting up constant pool..."); } setup(); if (verbose > 0) { Utils.log.info("Packing..."); } // writeFileHeader() is done last, since it has ultimate counts // writeBandHeaders() is called after all other bands are done writeConstantPool(); writeFiles(); writeAttrDefs(); writeInnerClasses(); writeClassesAndByteCodes(); writeAttrCounts(); if (verbose > 1) printCodeHist(); // choose codings (fill band_headers if needed) if (verbose > 0) { Utils.log.info("Coding..."); } all_bands.chooseBandCodings(); // now we can write the headers: writeFileHeader(); writeAllBandsTo(finalOut); ok = true; } catch (Exception ee) { Utils.log.warning("Error on output: "+ee, ee); //if (verbose > 0) ee.printStackTrace(); // Write partial output only if we are verbose. if (verbose > 0) finalOut.close(); if (ee instanceof IOException) throw (IOException)ee; if (ee instanceof RuntimeException) throw (RuntimeException)ee; throw new Error("error packing", ee); } } Set requiredEntries; // for the CP Map backCountTable; // for layout callables int[][] attrCounts; // count attr. occurences void setup() { requiredEntries = new HashSet<>(); setArchiveOptions(); trimClassAttributes(); collectAttributeLayouts(); pkg.buildGlobalConstantPool(requiredEntries); setBandIndexes(); makeNewAttributeBands(); collectInnerClasses(); } /* * Convenience function to choose an archive version based * on the class file versions observed within the archive * or set the user defined version preset via properties. */ void chooseDefaultPackageVersion() throws IOException { if (pkg.packageVersion != null) { packageVersion = pkg.packageVersion; if (verbose > 0) { Utils.log.info("package version overridden with: " + packageVersion); } return; } Package.Version highV = getHighestClassVersion(); // set the package version now if (highV.lessThan(JAVA6_MAX_CLASS_VERSION)) { // There are only old classfiles in this segment or resources packageVersion = JAVA5_PACKAGE_VERSION; } else if (highV.equals(JAVA6_MAX_CLASS_VERSION) || (highV.equals(JAVA7_MAX_CLASS_VERSION) && !pkg.cp.haveExtraTags())) { // force down the package version if we have jdk7 classes without // any Indy references, this is because jdk7 class file (52.0) without // Indy is identical to jdk6 class file (51.0). packageVersion = JAVA6_PACKAGE_VERSION; } else { // Normal case. Use the newest archive format, when available packageVersion = JAVA7_PACKAGE_VERSION; } if (verbose > 0) { Utils.log.info("Highest version class file: " + highV + " package version: " + packageVersion); } } void checkVersion() throws IOException { assert(packageVersion != null); if (packageVersion.lessThan(JAVA7_PACKAGE_VERSION)) { // this bit was reserved for future use in previous versions if (testBit(archiveOptions, AO_HAVE_CP_EXTRAS)) { throw new IOException("Format bits for Java 7 must be zero in previous releases"); } } if (testBit(archiveOptions, AO_UNUSED_MBZ)) { throw new IOException("High archive option bits are reserved and must be zero: " + Integer.toHexString(archiveOptions)); } } void setArchiveOptions() { // Decide on some archive options early. // Does not decide on: AO_HAVE_SPECIAL_FORMATS, // AO_HAVE_CP_NUMBERS, AO_HAVE_FILE_HEADERS. // Also, AO_HAVE_FILE_OPTIONS may be forced on later. int minModtime = pkg.default_modtime; int maxModtime = pkg.default_modtime; int minOptions = -1; int maxOptions = 0; // Import defaults from package (deflate hint, etc.). archiveOptions |= pkg.default_options; for (File file : pkg.files) { int modtime = file.modtime; int options = file.options; if (minModtime == NO_MODTIME) { minModtime = maxModtime = modtime; } else { if (minModtime > modtime) minModtime = modtime; if (maxModtime < modtime) maxModtime = modtime; } minOptions &= options; maxOptions |= options; } if (pkg.default_modtime == NO_MODTIME) { // Make everything else be a positive offset from here. pkg.default_modtime = minModtime; } if (minModtime != NO_MODTIME && minModtime != maxModtime) { // Put them into a band. archiveOptions |= AO_HAVE_FILE_MODTIME; } // If the archive deflation is set do not bother with each file. if (!testBit(archiveOptions,AO_DEFLATE_HINT) && minOptions != -1) { if (testBit(minOptions, FO_DEFLATE_HINT)) { // Every file has the deflate_hint set. // Set it for the whole archive, and omit options. archiveOptions |= AO_DEFLATE_HINT; minOptions -= FO_DEFLATE_HINT; maxOptions -= FO_DEFLATE_HINT; } pkg.default_options |= minOptions; if (minOptions != maxOptions || minOptions != pkg.default_options) { archiveOptions |= AO_HAVE_FILE_OPTIONS; } } // Decide on default version number (majority rule). Map verCounts = new HashMap<>(); int bestCount = 0; Package.Version bestVersion = null; for (Class cls : pkg.classes) { Package.Version version = cls.getVersion(); int[] var = verCounts.get(version); if (var == null) { var = new int[1]; verCounts.put(version, var); } int count = (var[0] += 1); //System.out.println("version="+version+" count="+count); if (bestCount < count) { bestCount = count; bestVersion = version; } } verCounts.clear(); if (bestVersion == null) bestVersion = JAVA_MIN_CLASS_VERSION; // degenerate case pkg.defaultClassVersion = bestVersion; if (verbose > 0) Utils.log.info("Consensus version number in segment is " + bestVersion); if (verbose > 0) Utils.log.info("Highest version number in segment is " + pkg.getHighestClassVersion()); // Now add explicit pseudo-attrs. to classes with odd versions. for (Class cls : pkg.classes) { if (!cls.getVersion().equals(bestVersion)) { Attribute a = makeClassFileVersionAttr(cls.getVersion()); if (verbose > 1) { Utils.log.fine("Version "+cls.getVersion() + " of " + cls + " doesn't match package version " + bestVersion); } // Note: Does not add in "natural" order. (Who cares?) cls.addAttribute(a); } } // Decide if we are transmitting a huge resource file: for (File file : pkg.files) { long len = file.getFileLength(); if (len != (int)len) { archiveOptions |= AO_HAVE_FILE_SIZE_HI; if (verbose > 0) Utils.log.info("Note: Huge resource file "+file.getFileName()+" forces 64-bit sizing"); break; } } // Decide if code attributes typically have sub-attributes. // In that case, to preserve compact 1-byte code headers, // we must declare unconditional presence of code flags. int cost0 = 0; int cost1 = 0; for (Class cls : pkg.classes) { for (Class.Method m : cls.getMethods()) { if (m.code != null) { if (m.code.attributeSize() == 0) { // cost of a useless unconditional flags byte cost1 += 1; } else if (shortCodeHeader(m.code) != LONG_CODE_HEADER) { // cost of inflating a short header cost0 += 3; } } } } if (cost0 > cost1) { archiveOptions |= AO_HAVE_ALL_CODE_FLAGS; } if (verbose > 0) Utils.log.info("archiveOptions = " +"0b"+Integer.toBinaryString(archiveOptions)); } void writeFileHeader() throws IOException { chooseDefaultPackageVersion(); writeArchiveMagic(); writeArchiveHeader(); } // Local routine used to format fixed-format scalars // in the file_header: private void putMagicInt32(int val) throws IOException { int res = val; for (int i = 0; i < 4; i++) { archive_magic.putByte(0xFF & (res >>> 24)); res <<= 8; } } void writeArchiveMagic() throws IOException { putMagicInt32(pkg.magic); } void writeArchiveHeader() throws IOException { // for debug only: number of words optimized away int headerSizeForDebug = AH_LENGTH_MIN; // AO_HAVE_SPECIAL_FORMATS is set if non-default // coding techniques are used, or if there are // compressor-defined attributes transmitted. boolean haveSpecial = testBit(archiveOptions, AO_HAVE_SPECIAL_FORMATS); if (!haveSpecial) { haveSpecial |= (band_headers.length() != 0); haveSpecial |= (attrDefsWritten.length != 0); if (haveSpecial) archiveOptions |= AO_HAVE_SPECIAL_FORMATS; } if (haveSpecial) headerSizeForDebug += AH_SPECIAL_FORMAT_LEN; // AO_HAVE_FILE_HEADERS is set if there is any // file or segment envelope information present. boolean haveFiles = testBit(archiveOptions, AO_HAVE_FILE_HEADERS); if (!haveFiles) { haveFiles |= (archiveNextCount > 0); haveFiles |= (pkg.default_modtime != NO_MODTIME); if (haveFiles) archiveOptions |= AO_HAVE_FILE_HEADERS; } if (haveFiles) headerSizeForDebug += AH_FILE_HEADER_LEN; // AO_HAVE_CP_NUMBERS is set if there are any numbers // in the global constant pool. (Numbers are in 15% of classes.) boolean haveNumbers = testBit(archiveOptions, AO_HAVE_CP_NUMBERS); if (!haveNumbers) { haveNumbers |= pkg.cp.haveNumbers(); if (haveNumbers) archiveOptions |= AO_HAVE_CP_NUMBERS; } if (haveNumbers) headerSizeForDebug += AH_CP_NUMBER_LEN; // AO_HAVE_CP_EXTRAS is set if there are constant pool entries // beyond the Java 6 version of the class file format. boolean haveCPExtra = testBit(archiveOptions, AO_HAVE_CP_EXTRAS); if (!haveCPExtra) { haveCPExtra |= pkg.cp.haveExtraTags(); if (haveCPExtra) archiveOptions |= AO_HAVE_CP_EXTRAS; } if (haveCPExtra) headerSizeForDebug += AH_CP_EXTRA_LEN; // the archiveOptions are all initialized, sanity check now!. checkVersion(); archive_header_0.putInt(packageVersion.minor); archive_header_0.putInt(packageVersion.major); if (verbose > 0) Utils.log.info("Package Version for this segment:" + packageVersion); archive_header_0.putInt(archiveOptions); // controls header format assert(archive_header_0.length() == AH_LENGTH_0); final int DUMMY = 0; if (haveFiles) { assert(archive_header_S.length() == AH_ARCHIVE_SIZE_HI); archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 32) assert(archive_header_S.length() == AH_ARCHIVE_SIZE_LO); archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 0) assert(archive_header_S.length() == AH_LENGTH_S); } // Done with unsized part of header.... if (haveFiles) { archive_header_1.putInt(archiveNextCount); // usually zero archive_header_1.putInt(pkg.default_modtime); archive_header_1.putInt(pkg.files.size()); } else { assert(pkg.files.isEmpty()); } if (haveSpecial) { archive_header_1.putInt(band_headers.length()); archive_header_1.putInt(attrDefsWritten.length); } else { assert(band_headers.length() == 0); assert(attrDefsWritten.length == 0); } writeConstantPoolCounts(haveNumbers, haveCPExtra); archive_header_1.putInt(pkg.getAllInnerClasses().size()); archive_header_1.putInt(pkg.defaultClassVersion.minor); archive_header_1.putInt(pkg.defaultClassVersion.major); archive_header_1.putInt(pkg.classes.size()); // Sanity: Make sure we came out to 29 (less optional fields): assert(archive_header_0.length() + archive_header_S.length() + archive_header_1.length() == headerSizeForDebug); // Figure out all the sizes now, first cut: archiveSize0 = 0; archiveSize1 = all_bands.outputSize(); // Second cut: archiveSize0 += archive_magic.outputSize(); archiveSize0 += archive_header_0.outputSize(); archiveSize0 += archive_header_S.outputSize(); // Make the adjustments: archiveSize1 -= archiveSize0; // Patch the header: if (haveFiles) { int archiveSizeHi = (int)(archiveSize1 >>> 32); int archiveSizeLo = (int)(archiveSize1 >>> 0); archive_header_S.patchValue(AH_ARCHIVE_SIZE_HI, archiveSizeHi); archive_header_S.patchValue(AH_ARCHIVE_SIZE_LO, archiveSizeLo); int zeroLen = UNSIGNED5.getLength(DUMMY); archiveSize0 += UNSIGNED5.getLength(archiveSizeHi) - zeroLen; archiveSize0 += UNSIGNED5.getLength(archiveSizeLo) - zeroLen; } if (verbose > 1) Utils.log.fine("archive sizes: "+ archiveSize0+"+"+archiveSize1); assert(all_bands.outputSize() == archiveSize0+archiveSize1); } void writeConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException { for (byte tag : ConstantPool.TAGS_IN_ORDER) { int count = pkg.cp.getIndexByTag(tag).size(); switch (tag) { case CONSTANT_Utf8: // The null string is always first. if (count > 0) assert(pkg.cp.getIndexByTag(tag).get(0) == ConstantPool.getUtf8Entry("")); break; case CONSTANT_Integer: case CONSTANT_Float: case CONSTANT_Long: case CONSTANT_Double: // Omit counts for numbers if possible. if (!haveNumbers) { assert(count == 0); continue; } break; case CONSTANT_MethodHandle: case CONSTANT_MethodType: case CONSTANT_InvokeDynamic: case CONSTANT_BootstrapMethod: // Omit counts for newer entities if possible. if (!haveCPExtra) { assert(count == 0); continue; } break; } archive_header_1.putInt(count); } } protected Index getCPIndex(byte tag) { return pkg.cp.getIndexByTag(tag); } // (The following observations are out of date; they apply only to // "banding" the constant pool itself. Later revisions of this algorithm // applied the banding technique to every part of the package file, // applying the benefits more broadly.) // Note: Keeping the data separate in passes (or "bands") allows the // compressor to issue significantly shorter indexes for repeated data. // The difference in zipped size is 4%, which is remarkable since the // unzipped sizes are the same (only the byte order differs). // After moving similar data into bands, it becomes natural to delta-encode // each band. (This is especially useful if we sort the constant pool first.) // Delta encoding saves an extra 5% in the output size (13% of the CP itself). // Because a typical delta usees much less data than a byte, the savings after // zipping is even better: A zipped delta-encoded package is 8% smaller than // a zipped non-delta-encoded package. Thus, in the zipped file, a banded, // delta-encoded constant pool saves over 11% (of the total file size) compared // with a zipped unbanded file. void writeConstantPool() throws IOException { IndexGroup cp = pkg.cp; if (verbose > 0) Utils.log.info("Writing CP"); for (byte tag : ConstantPool.TAGS_IN_ORDER) { Index index = cp.getIndexByTag(tag); Entry[] cpMap = index.cpMap; if (verbose > 0) Utils.log.info("Writing "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries..."); if (optDumpBands) { try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) { printArrayTo(ps, cpMap, 0, cpMap.length); } } switch (tag) { case CONSTANT_Utf8: writeUtf8Bands(cpMap); break; case CONSTANT_Integer: for (int i = 0; i < cpMap.length; i++) { NumberEntry e = (NumberEntry) cpMap[i]; int x = ((Integer)e.numberValue()).intValue(); cp_Int.putInt(x); } break; case CONSTANT_Float: for (int i = 0; i < cpMap.length; i++) { NumberEntry e = (NumberEntry) cpMap[i]; float fx = ((Float)e.numberValue()).floatValue(); int x = Float.floatToIntBits(fx); cp_Float.putInt(x); } break; case CONSTANT_Long: for (int i = 0; i < cpMap.length; i++) { NumberEntry e = (NumberEntry) cpMap[i]; long x = ((Long)e.numberValue()).longValue(); cp_Long_hi.putInt((int)(x >>> 32)); cp_Long_lo.putInt((int)(x >>> 0)); } break; case CONSTANT_Double: for (int i = 0; i < cpMap.length; i++) { NumberEntry e = (NumberEntry) cpMap[i]; double dx = ((Double)e.numberValue()).doubleValue(); long x = Double.doubleToLongBits(dx); cp_Double_hi.putInt((int)(x >>> 32)); cp_Double_lo.putInt((int)(x >>> 0)); } break; case CONSTANT_String: for (int i = 0; i < cpMap.length; i++) { StringEntry e = (StringEntry) cpMap[i]; cp_String.putRef(e.ref); } break; case CONSTANT_Class: for (int i = 0; i < cpMap.length; i++) { ClassEntry e = (ClassEntry) cpMap[i]; cp_Class.putRef(e.ref); } break; case CONSTANT_Signature: writeSignatureBands(cpMap); break; case CONSTANT_NameandType: for (int i = 0; i < cpMap.length; i++) { DescriptorEntry e = (DescriptorEntry) cpMap[i]; cp_Descr_name.putRef(e.nameRef); cp_Descr_type.putRef(e.typeRef); } break; case CONSTANT_Fieldref: writeMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc); break; case CONSTANT_Methodref: writeMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc); break; case CONSTANT_InterfaceMethodref: writeMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc); break; case CONSTANT_MethodHandle: for (int i = 0; i < cpMap.length; i++) { MethodHandleEntry e = (MethodHandleEntry) cpMap[i]; cp_MethodHandle_refkind.putInt(e.refKind); cp_MethodHandle_member.putRef(e.memRef); } break; case CONSTANT_MethodType: for (int i = 0; i < cpMap.length; i++) { MethodTypeEntry e = (MethodTypeEntry) cpMap[i]; cp_MethodType.putRef(e.typeRef); } break; case CONSTANT_InvokeDynamic: for (int i = 0; i < cpMap.length; i++) { InvokeDynamicEntry e = (InvokeDynamicEntry) cpMap[i]; cp_InvokeDynamic_spec.putRef(e.bssRef); cp_InvokeDynamic_desc.putRef(e.descRef); } break; case CONSTANT_BootstrapMethod: for (int i = 0; i < cpMap.length; i++) { BootstrapMethodEntry e = (BootstrapMethodEntry) cpMap[i]; cp_BootstrapMethod_ref.putRef(e.bsmRef); cp_BootstrapMethod_arg_count.putInt(e.argRefs.length); for (Entry argRef : e.argRefs) { cp_BootstrapMethod_arg.putRef(argRef); } } break; default: throw new AssertionError("unexpected CP tag in package"); } } if (optDumpBands || verbose > 1) { for (byte tag = CONSTANT_GroupFirst; tag < CONSTANT_GroupLimit; tag++) { Index index = cp.getIndexByTag(tag); if (index == null || index.isEmpty()) continue; Entry[] cpMap = index.cpMap; if (verbose > 1) Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries."); if (optDumpBands) { try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) { printArrayTo(ps, cpMap, 0, cpMap.length, true); } } } } } void writeUtf8Bands(Entry[] cpMap) throws IOException { if (cpMap.length == 0) return; // nothing to write // The first element must always be the empty string. assert(cpMap[0].stringValue().equals("")); final int SUFFIX_SKIP_1 = 1; final int PREFIX_SKIP_2 = 2; // Fetch the char arrays, first of all. char[][] chars = new char[cpMap.length][]; for (int i = 0; i < chars.length; i++) { chars[i] = cpMap[i].stringValue().toCharArray(); } // First band: Write lengths of shared prefixes. int[] prefixes = new int[cpMap.length]; // includes 2 skipped zeroes char[] prevChars = {}; for (int i = 0; i < chars.length; i++) { int prefix = 0; char[] curChars = chars[i]; int limit = Math.min(curChars.length, prevChars.length); while (prefix < limit && curChars[prefix] == prevChars[prefix]) prefix++; prefixes[i] = prefix; if (i >= PREFIX_SKIP_2) cp_Utf8_prefix.putInt(prefix); else assert(prefix == 0); prevChars = curChars; } // Second band: Write lengths of unshared suffixes. // Third band: Write the char values in the unshared suffixes. for (int i = 0; i < chars.length; i++) { char[] str = chars[i]; int prefix = prefixes[i]; int suffix = str.length - prefixes[i]; boolean isPacked = false; if (suffix == 0) { // Zero suffix length is special flag to indicate // separate treatment in cp_Utf8_big bands. // This suffix length never occurs naturally, // except in the one case of a zero-length string. // (If it occurs, it is the first, due to sorting.) // The zero length string must, paradoxically, be // encoded as a zero-length cp_Utf8_big band. // This wastes exactly (& tolerably) one null byte. isPacked = (i >= SUFFIX_SKIP_1); // Do not bother to add an empty "(Utf8_big_0)" band. // Also, the initial empty string does not require a band. } else if (optBigStrings && effort > 1 && suffix > 100) { int numWide = 0; for (int n = 0; n < suffix; n++) { if (str[prefix+n] > 127) { numWide++; } } if (numWide > 100) { // Try packing the chars with an alternate encoding. isPacked = tryAlternateEncoding(i, numWide, str, prefix); } } if (i < SUFFIX_SKIP_1) { // No output. assert(!isPacked); assert(suffix == 0); } else if (isPacked) { // Mark packed string with zero-length suffix count. // This tells the unpacker to go elsewhere for the suffix bits. // Fourth band: Write unshared suffix with alternate coding. cp_Utf8_suffix.putInt(0); cp_Utf8_big_suffix.putInt(suffix); } else { assert(suffix != 0); // would be ambiguous // Normal string. Save suffix in third and fourth bands. cp_Utf8_suffix.putInt(suffix); for (int n = 0; n < suffix; n++) { int ch = str[prefix+n]; cp_Utf8_chars.putInt(ch); } } } if (verbose > 0) { int normCharCount = cp_Utf8_chars.length(); int packCharCount = cp_Utf8_big_chars.length(); int charCount = normCharCount + packCharCount; Utils.log.info("Utf8string #CHARS="+charCount+" #PACKEDCHARS="+packCharCount); } } private boolean tryAlternateEncoding(int i, int numWide, char[] str, int prefix) { int suffix = str.length - prefix; int[] cvals = new int[suffix]; for (int n = 0; n < suffix; n++) { cvals[n] = str[prefix+n]; } CodingChooser cc = getCodingChooser(); Coding bigRegular = cp_Utf8_big_chars.regularCoding; String bandName = "(Utf8_big_"+i+")"; int[] sizes = { 0, 0 }; final int BYTE_SIZE = CodingChooser.BYTE_SIZE; final int ZIP_SIZE = CodingChooser.ZIP_SIZE; if (verbose > 1 || cc.verbose > 1) { Utils.log.fine("--- chooseCoding "+bandName); } CodingMethod special = cc.choose(cvals, bigRegular, sizes); Coding charRegular = cp_Utf8_chars.regularCoding; if (verbose > 1) Utils.log.fine("big string["+i+"] len="+suffix+" #wide="+numWide+" size="+sizes[BYTE_SIZE]+"/z="+sizes[ZIP_SIZE]+" coding "+special); if (special != charRegular) { int specialZipSize = sizes[ZIP_SIZE]; int[] normalSizes = cc.computeSize(charRegular, cvals); int normalZipSize = normalSizes[ZIP_SIZE]; int minWin = Math.max(5, normalZipSize/1000); if (verbose > 1) Utils.log.fine("big string["+i+"] normalSize="+normalSizes[BYTE_SIZE]+"/z="+normalSizes[ZIP_SIZE]+" win="+(specialZipSize>> 32)); if (haveModtime) file_modtime.putInt(file.modtime - pkg.default_modtime); if (haveOptions) file_options.putInt(file.options); file.writeTo(file_bits.collectorStream()); if (verbose > 1) Utils.log.fine("Wrote "+len+" bytes of "+file.name.stringValue()); } if (verbose > 0) Utils.log.info("Wrote "+numFiles+" resource files"); } void collectAttributeLayouts() { maxFlags = new int[ATTR_CONTEXT_LIMIT]; allLayouts = new FixedList<>(ATTR_CONTEXT_LIMIT); for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { allLayouts.set(i, new HashMap()); } // Collect maxFlags and allLayouts. for (Class cls : pkg.classes) { visitAttributeLayoutsIn(ATTR_CONTEXT_CLASS, cls); for (Class.Field f : cls.getFields()) { visitAttributeLayoutsIn(ATTR_CONTEXT_FIELD, f); } for (Class.Method m : cls.getMethods()) { visitAttributeLayoutsIn(ATTR_CONTEXT_METHOD, m); if (m.code != null) { visitAttributeLayoutsIn(ATTR_CONTEXT_CODE, m.code); } } } // If there are many species of attributes, use 63-bit flags. for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { int nl = allLayouts.get(i).size(); boolean haveLongFlags = haveFlagsHi(i); final int TOO_MANY_ATTRS = 32 /*int flag size*/ - 12 /*typical flag bits in use*/ + 4 /*typical number of OK overflows*/; if (nl >= TOO_MANY_ATTRS) { // heuristic int mask = 1<<(LG_AO_HAVE_XXX_FLAGS_HI+i); archiveOptions |= mask; haveLongFlags = true; if (verbose > 0) Utils.log.info("Note: Many "+Attribute.contextName(i)+" attributes forces 63-bit flags"); } if (verbose > 1) { Utils.log.fine(Attribute.contextName(i)+".maxFlags = 0x"+Integer.toHexString(maxFlags[i])); Utils.log.fine(Attribute.contextName(i)+".#layouts = "+nl); } assert(haveFlagsHi(i) == haveLongFlags); } initAttrIndexLimit(); // Standard indexes can never conflict with flag bits. Assert it. for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { assert((attrFlagMask[i] & maxFlags[i]) == 0); } // Collect counts for both predefs. and custom defs. // Decide on custom, local attribute definitions. backCountTable = new HashMap<>(); attrCounts = new int[ATTR_CONTEXT_LIMIT][]; for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { // Now the remaining defs in allLayouts[i] need attr. indexes. // Fill up unused flag bits with new defs. // Unused bits are those which are not used by predefined attrs, // and which are always clear in the classfiles. long avHiBits = ~(maxFlags[i] | attrFlagMask[i]); assert(attrIndexLimit[i] > 0); assert(attrIndexLimit[i] < 64); // all bits fit into a Java long avHiBits &= (1L< defMap = allLayouts.get(i); @SuppressWarnings({"unchecked", "rawtypes"}) Map.Entry[] layoutsAndCounts = new Map.Entry[defMap.size()]; defMap.entrySet().toArray(layoutsAndCounts); // Sort by count, most frequent first. // Predefs. participate in this sort, though it does not matter. Arrays.sort(layoutsAndCounts, new Comparator>() { public int compare(Map.Entry e0, Map.Entry e1) { // Primary sort key is count, reversed. int r = -(e0.getValue()[0] - e1.getValue()[0]); if (r != 0) return r; return e0.getKey().compareTo(e1.getKey()); } }); attrCounts[i] = new int[attrIndexLimit[i]+layoutsAndCounts.length]; for (int j = 0; j < layoutsAndCounts.length; j++) { Map.Entry e = layoutsAndCounts[j]; Attribute.Layout def = e.getKey(); int count = e.getValue()[0]; int index; Integer predefIndex = attrIndexTable.get(def); if (predefIndex != null) { // The index is already set. index = predefIndex.intValue(); } else if (avHiBits != 0) { while ((avHiBits & 1) == 0) { avHiBits >>>= 1; nextLoBit += 1; } avHiBits -= 1; // clear low bit; we are using it now // Update attrIndexTable: index = setAttributeLayoutIndex(def, nextLoBit); } else { // Update attrIndexTable: index = setAttributeLayoutIndex(def, ATTR_INDEX_OVERFLOW); } // Now that we know the index, record the count of this def. attrCounts[i][index] = count; // For all callables in the def, keep a tally of back-calls. Attribute.Layout.Element[] cbles = def.getCallables(); final int[] bc = new int[cbles.length]; for (int k = 0; k < cbles.length; k++) { assert(cbles[k].kind == Attribute.EK_CBLE); if (!cbles[k].flagTest(Attribute.EF_BACK)) { bc[k] = -1; // no count to accumulate here } } backCountTable.put(def, bc); if (predefIndex == null) { // Make sure the package CP can name the local attribute. Entry ne = ConstantPool.getUtf8Entry(def.name()); String layout = def.layoutForClassVersion(getHighestClassVersion()); Entry le = ConstantPool.getUtf8Entry(layout); requiredEntries.add(ne); requiredEntries.add(le); if (verbose > 0) { if (index < attrIndexLimit[i]) Utils.log.info("Using free flag bit 1<<"+index+" for "+count+" occurrences of "+def); else Utils.log.info("Using overflow index "+index+" for "+count+" occurrences of "+def); } } } } // Later, when emitting attr_definition_bands, we will look at // attrDefSeen and attrDefs at position 32/63 and beyond. // The attrIndexTable will provide elements of xxx_attr_indexes bands. // Done with scratch variables: maxFlags = null; allLayouts = null; } // Scratch variables for processing attributes and flags. int[] maxFlags; List> allLayouts; void visitAttributeLayoutsIn(int ctype, Attribute.Holder h) { // Make note of which flags appear in the class file. // Set them in maxFlags. maxFlags[ctype] |= h.flags; for (Attribute a : h.getAttributes()) { Attribute.Layout def = a.layout(); Map defMap = allLayouts.get(ctype); int[] count = defMap.get(def); if (count == null) { defMap.put(def, count = new int[1]); } if (count[0] < Integer.MAX_VALUE) { count[0] += 1; } } } Attribute.Layout[] attrDefsWritten; void writeAttrDefs() throws IOException { List defList = new ArrayList<>(); for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) { int limit = attrDefs.get(i).size(); for (int j = 0; j < limit; j++) { int header = i; // ctype if (j < attrIndexLimit[i]) { header |= ((j + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT); assert(header < 0x100); // must fit into a byte // (...else header is simply ctype, with zero high bits.) if (!testBit(attrDefSeen[i], 1L<() { public int compare(Object[] a0, Object[] a1) { // Primary sort key is attr def header. @SuppressWarnings("unchecked") int r = ((Comparable)a0[0]).compareTo(a1[0]); if (r != 0) return r; Integer ind0 = attrIndexTable.get(a0[1]); Integer ind1 = attrIndexTable.get(a1[1]); // Secondary sort key is attribute index. // (This must be so, in order to keep overflow attr order.) assert(ind0 != null); assert(ind1 != null); return ind0.compareTo(ind1); } }); attrDefsWritten = new Attribute.Layout[numAttrDefs]; try (PrintStream dump = !optDumpBands ? null : new PrintStream(getDumpStream(attr_definition_headers, ".def"))) { int[] indexForDebug = Arrays.copyOf(attrIndexLimit, ATTR_CONTEXT_LIMIT); for (int i = 0; i < defs.length; i++) { int header = ((Integer)defs[i][0]).intValue(); Attribute.Layout def = (Attribute.Layout) defs[i][1]; attrDefsWritten[i] = def; assert((header & ADH_CONTEXT_MASK) == def.ctype()); attr_definition_headers.putByte(header); attr_definition_name.putRef(ConstantPool.getUtf8Entry(def.name())); String layout = def.layoutForClassVersion(getHighestClassVersion()); attr_definition_layout.putRef(ConstantPool.getUtf8Entry(layout)); // Check that we are transmitting that correct attribute index: boolean debug = false; assert(debug = true); if (debug) { int hdrIndex = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; if (hdrIndex < 0) hdrIndex = indexForDebug[def.ctype()]++; int realIndex = (attrIndexTable.get(def)).intValue(); assert(hdrIndex == realIndex); } if (dump != null) { int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; dump.println(index+" "+def); } } } } void writeAttrCounts() throws IOException { // Write the four xxx_attr_calls bands. for (int ctype = 0; ctype < ATTR_CONTEXT_LIMIT; ctype++) { MultiBand xxx_attr_bands = attrBands[ctype]; IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS); Attribute.Layout[] defs = new Attribute.Layout[attrDefs.get(ctype).size()]; attrDefs.get(ctype).toArray(defs); for (boolean predef = true; ; predef = false) { for (int ai = 0; ai < defs.length; ai++) { Attribute.Layout def = defs[ai]; if (def == null) continue; // unused index if (predef != isPredefinedAttr(ctype, ai)) continue; // wrong pass int totalCount = attrCounts[ctype][ai]; if (totalCount == 0) continue; // irrelevant int[] bc = backCountTable.get(def); for (int j = 0; j < bc.length; j++) { if (bc[j] >= 0) { int backCount = bc[j]; bc[j] = -1; // close out; do not collect further counts xxx_attr_calls.putInt(backCount); assert(def.getCallables()[j].flagTest(Attribute.EF_BACK)); } else { assert(!def.getCallables()[j].flagTest(Attribute.EF_BACK)); } } } if (!predef) break; } } } void trimClassAttributes() { for (Class cls : pkg.classes) { // Replace "obvious" SourceFile attrs by null. cls.minimizeSourceFile(); // BootstrapMethods should never have been inserted. assert(cls.getAttribute(Package.attrBootstrapMethodsEmpty) == null); } } void collectInnerClasses() { // Capture inner classes, removing them from individual classes. // Irregular inner classes must stay local, though. Map allICMap = new HashMap<>(); // First, collect a consistent global set. for (Class cls : pkg.classes) { if (!cls.hasInnerClasses()) continue; for (InnerClass ic : cls.getInnerClasses()) { InnerClass pic = allICMap.put(ic.thisClass, ic); if (pic != null && !pic.equals(ic) && pic.predictable) { // Different ICs. Choose the better to make global. allICMap.put(pic.thisClass, pic); } } } InnerClass[] allICs = new InnerClass[allICMap.size()]; allICMap.values().toArray(allICs); allICMap = null; // done with it // Note: The InnerClasses attribute must be in a valid order, // so that A$B always occurs earlier than A$B$C. This is an // important side-effect of sorting lexically by class name. Arrays.sort(allICs); // put in canonical order pkg.setAllInnerClasses(Arrays.asList(allICs)); // Next, empty out of every local set the consistent entries. // Calculate whether there is any remaining need to have a local // set, and whether it needs to be locked. for (Class cls : pkg.classes) { cls.minimizeLocalICs(); } } void writeInnerClasses() throws IOException { for (InnerClass ic : pkg.getAllInnerClasses()) { int flags = ic.flags; assert((flags & ACC_IC_LONG_FORM) == 0); if (!ic.predictable) { flags |= ACC_IC_LONG_FORM; } ic_this_class.putRef(ic.thisClass); ic_flags.putInt(flags); if (!ic.predictable) { ic_outer_class.putRef(ic.outerClass); ic_name.putRef(ic.name); } } } /** If there are any extra InnerClasses entries to write which are * not already implied by the global table, put them into a * local attribute. This is expected to be rare. */ void writeLocalInnerClasses(Class cls) throws IOException { List localICs = cls.getInnerClasses(); class_InnerClasses_N.putInt(localICs.size()); for(InnerClass ic : localICs) { class_InnerClasses_RC.putRef(ic.thisClass); // Is it redundant with the global version? if (ic.equals(pkg.getGlobalInnerClass(ic.thisClass))) { // A zero flag means copy a global IC here. class_InnerClasses_F.putInt(0); } else { int flags = ic.flags; if (flags == 0) flags = ACC_IC_LONG_FORM; // force it to be non-zero class_InnerClasses_F.putInt(flags); class_InnerClasses_outer_RCN.putRef(ic.outerClass); class_InnerClasses_name_RUN.putRef(ic.name); } } } void writeClassesAndByteCodes() throws IOException { Class[] classes = new Class[pkg.classes.size()]; pkg.classes.toArray(classes); // Note: This code respects the order in which caller put classes. if (verbose > 0) Utils.log.info(" ...scanning "+classes.length+" classes..."); int nwritten = 0; for (int i = 0; i < classes.length; i++) { // Collect the class body, sans bytecodes. Class cls = classes[i]; if (verbose > 1) Utils.log.fine("Scanning "+cls); ClassEntry thisClass = cls.thisClass; ClassEntry superClass = cls.superClass; ClassEntry[] interfaces = cls.interfaces; // Encode rare case of null superClass as thisClass: assert(superClass != thisClass); // bad class file!? if (superClass == null) superClass = thisClass; class_this.putRef(thisClass); class_super.putRef(superClass); class_interface_count.putInt(cls.interfaces.length); for (int j = 0; j < interfaces.length; j++) { class_interface.putRef(interfaces[j]); } writeMembers(cls); writeAttrs(ATTR_CONTEXT_CLASS, cls, cls); nwritten++; if (verbose > 0 && (nwritten % 1000) == 0) Utils.log.info("Have scanned "+nwritten+" classes..."); } } void writeMembers(Class cls) throws IOException { List fields = cls.getFields(); class_field_count.putInt(fields.size()); for (Class.Field f : fields) { field_descr.putRef(f.getDescriptor()); writeAttrs(ATTR_CONTEXT_FIELD, f, cls); } List methods = cls.getMethods(); class_method_count.putInt(methods.size()); for (Class.Method m : methods) { method_descr.putRef(m.getDescriptor()); writeAttrs(ATTR_CONTEXT_METHOD, m, cls); assert((m.code != null) == (m.getAttribute(attrCodeEmpty) != null)); if (m.code != null) { writeCodeHeader(m.code); writeByteCodes(m.code); } } } void writeCodeHeader(Code c) throws IOException { boolean attrsOK = testBit(archiveOptions, AO_HAVE_ALL_CODE_FLAGS); int na = c.attributeSize(); int sc = shortCodeHeader(c); if (!attrsOK && na > 0) // We must write flags, and can only do so for long headers. sc = LONG_CODE_HEADER; if (verbose > 2) { int siglen = c.getMethod().getArgumentSize(); Utils.log.fine("Code sizes info "+c.max_stack+" "+c.max_locals+" "+c.getHandlerCount()+" "+siglen+" "+na+(sc > 0 ? " SHORT="+sc : "")); } code_headers.putByte(sc); if (sc == LONG_CODE_HEADER) { code_max_stack.putInt(c.getMaxStack()); code_max_na_locals.putInt(c.getMaxNALocals()); code_handler_count.putInt(c.getHandlerCount()); } else { assert(attrsOK || na == 0); assert(c.getHandlerCount() < shortCodeHeader_h_limit); } writeCodeHandlers(c); if (sc == LONG_CODE_HEADER || attrsOK) writeAttrs(ATTR_CONTEXT_CODE, c, c.thisClass()); } void writeCodeHandlers(Code c) throws IOException { int sum, del; for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { code_handler_class_RCN.putRef(c.handler_class[j]); // null OK // Encode end as offset from start, and catch as offset from end, // because they are strongly correlated. sum = c.encodeBCI(c.handler_start[j]); code_handler_start_P.putInt(sum); del = c.encodeBCI(c.handler_end[j]) - sum; code_handler_end_PO.putInt(del); sum += del; del = c.encodeBCI(c.handler_catch[j]) - sum; code_handler_catch_PO.putInt(del); } } // Generic routines for writing attributes and flags of // classes, fields, methods, and codes. void writeAttrs(int ctype, final Attribute.Holder h, Class cls) throws IOException { MultiBand xxx_attr_bands = attrBands[ctype]; IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI); IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO); boolean haveLongFlags = haveFlagsHi(ctype); assert(attrIndexLimit[ctype] == (haveLongFlags? 63: 32)); if (h.attributes == null) { xxx_flags_lo.putInt(h.flags); // no extra bits to set here if (haveLongFlags) xxx_flags_hi.putInt(0); return; } if (verbose > 3) Utils.log.fine("Transmitting attrs for "+h+" flags="+Integer.toHexString(h.flags)); long flagMask = attrFlagMask[ctype]; // which flags are attr bits? long flagsToAdd = 0; int overflowCount = 0; for (Attribute a : h.attributes) { Attribute.Layout def = a.layout(); int index = (attrIndexTable.get(def)).intValue(); assert(attrDefs.get(ctype).get(index) == def); if (verbose > 3) Utils.log.fine("add attr @"+index+" "+a+" in "+h); if (index < attrIndexLimit[ctype] && testBit(flagMask, 1L< 3) Utils.log.fine("Adding flag bit 1<<"+index+" in "+Long.toHexString(flagMask)); assert(!testBit(h.flags, 1L< 3) Utils.log.fine("Adding overflow attr #"+overflowCount); IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES); xxx_attr_indexes.putInt(index); // System.out.println("overflow @"+index); } if (def.bandCount == 0) { if (def == attrInnerClassesEmpty) { // Special logic to write this attr. writeLocalInnerClasses((Class) h); continue; } // Empty attr; nothing more to write here. continue; } assert(a.fixups == null); final Band[] ab = attrBandTable.get(def); assert(ab != null); assert(ab.length == def.bandCount); final int[] bc = backCountTable.get(def); assert(bc != null); assert(bc.length == def.getCallables().length); // Write one attribute of type def into ab. if (verbose > 2) Utils.log.fine("writing "+a+" in "+h); boolean isCV = (ctype == ATTR_CONTEXT_FIELD && def == attrConstantValue); if (isCV) setConstantValueIndex((Class.Field)h); a.parse(cls, a.bytes(), 0, a.size(), new Attribute.ValueStream() { public void putInt(int bandIndex, int value) { ((IntBand) ab[bandIndex]).putInt(value); } public void putRef(int bandIndex, Entry ref) { ((CPRefBand) ab[bandIndex]).putRef(ref); } public int encodeBCI(int bci) { Code code = (Code) h; return code.encodeBCI(bci); } public void noteBackCall(int whichCallable) { assert(bc[whichCallable] >= 0); bc[whichCallable] += 1; } }); if (isCV) setConstantValueIndex(null); // clean up } if (overflowCount > 0) { IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT); xxx_attr_count.putInt(overflowCount); } xxx_flags_lo.putInt(h.flags | (int)flagsToAdd); if (haveLongFlags) xxx_flags_hi.putInt((int)(flagsToAdd >>> 32)); else assert((flagsToAdd >>> 32) == 0); assert((h.flags & flagsToAdd) == 0) : (h+".flags=" +Integer.toHexString(h.flags)+"^" +Long.toHexString(flagsToAdd)); } // temporary scratch variables for processing code blocks private Code curCode; private Class curClass; private Entry[] curCPMap; private void beginCode(Code c) { assert(curCode == null); curCode = c; curClass = c.m.thisClass(); curCPMap = c.getCPMap(); } private void endCode() { curCode = null; curClass = null; curCPMap = null; } // Return an _invokeinit_op variant, if the instruction matches one, // else -1. private int initOpVariant(Instruction i, Entry newClass) { if (i.getBC() != _invokespecial) return -1; MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); if ("".equals(ref.descRef.nameRef.stringValue()) == false) return -1; ClassEntry refClass = ref.classRef; if (refClass == curClass.thisClass) return _invokeinit_op+_invokeinit_self_option; if (refClass == curClass.superClass) return _invokeinit_op+_invokeinit_super_option; if (refClass == newClass) return _invokeinit_op+_invokeinit_new_option; return -1; } // Return a _self_linker_op variant, if the instruction matches one, // else -1. private int selfOpVariant(Instruction i) { int bc = i.getBC(); if (!(bc >= _first_linker_op && bc <= _last_linker_op)) return -1; MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); ClassEntry refClass = ref.classRef; int self_bc = _self_linker_op + (bc - _first_linker_op); if (refClass == curClass.thisClass) return self_bc; if (refClass == curClass.superClass) return self_bc + _self_linker_super_flag; return -1; } void writeByteCodes(Code code) throws IOException { beginCode(code); IndexGroup cp = pkg.cp; // true if the previous instruction is an aload to absorb boolean prevAload = false; // class of most recent new; helps compress calls Entry newClass = null; for (Instruction i = code.instructionAt(0); i != null; i = i.next()) { // %%% Add a stress mode which issues _ref/_byte_escape. if (verbose > 3) Utils.log.fine(i.toString()); if (i.isNonstandard()) { // Crash and burn with a complaint if there are funny // bytecodes in this class file. String complaint = code.getMethod() +" contains an unrecognized bytecode "+i +"; please use the pass-file option on this class."; Utils.log.warning(complaint); throw new IOException(complaint); } if (i.isWide()) { if (verbose > 1) { Utils.log.fine("_wide opcode in "+code); Utils.log.fine(i.toString()); } bc_codes.putByte(_wide); codeHist[_wide]++; } int bc = i.getBC(); // Begin "bc_linker" compression. if (bc == _aload_0) { // Try to group aload_0 with a following operation. Instruction ni = code.instructionAt(i.getNextPC()); if (selfOpVariant(ni) >= 0) { prevAload = true; continue; } } // Test for invocations: int init_bc = initOpVariant(i, newClass); if (init_bc >= 0) { if (prevAload) { // get rid of it bc_codes.putByte(_aload_0); codeHist[_aload_0]++; prevAload = false; //used up } // Write special bytecode. bc_codes.putByte(init_bc); codeHist[init_bc]++; MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); // Write operand to a separate band. int coding = cp.getOverloadingIndex(ref); bc_initref.putInt(coding); continue; } int self_bc = selfOpVariant(i); if (self_bc >= 0) { boolean isField = Instruction.isFieldOp(bc); boolean isSuper = (self_bc >= _self_linker_op+_self_linker_super_flag); boolean isAload = prevAload; prevAload = false; //used up if (isAload) self_bc += _self_linker_aload_flag; // Write special bytecode. bc_codes.putByte(self_bc); codeHist[self_bc]++; // Write field or method ref to a separate band. MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap); CPRefBand bc_which = selfOpRefBand(self_bc); Index which_ix = cp.getMemberIndex(ref.tag, ref.classRef); bc_which.putRef(ref, which_ix); continue; } assert(!prevAload); // End "bc_linker" compression. // Normal bytecode. codeHist[bc]++; switch (bc) { case _tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) case _lookupswitch: // apc: (df, nc, nc*(case, label)) bc_codes.putByte(bc); Instruction.Switch isw = (Instruction.Switch) i; // Note that we do not write the alignment bytes. int apc = isw.getAlignedPC(); int npc = isw.getNextPC(); // write a length specification into the bytecode stream int caseCount = isw.getCaseCount(); bc_case_count.putInt(caseCount); putLabel(bc_label, code, i.getPC(), isw.getDefaultLabel()); for (int j = 0; j < caseCount; j++) { putLabel(bc_label, code, i.getPC(), isw.getCaseLabel(j)); } // Transmit case values in their own band. if (bc == _tableswitch) { bc_case_value.putInt(isw.getCaseValue(0)); } else { for (int j = 0; j < caseCount; j++) { bc_case_value.putInt(isw.getCaseValue(j)); } } // Done with the switch. continue; } int branch = i.getBranchLabel(); if (branch >= 0) { bc_codes.putByte(bc); putLabel(bc_label, code, i.getPC(), branch); continue; } Entry ref = i.getCPRef(curCPMap); if (ref != null) { if (bc == _new) newClass = ref; if (bc == _ldc) ldcHist[ref.tag]++; CPRefBand bc_which; int vbc = bc; switch (i.getCPTag()) { case CONSTANT_LoadableValue: switch (ref.tag) { case CONSTANT_Integer: bc_which = bc_intref; switch (bc) { case _ldc: vbc = _ildc; break; case _ldc_w: vbc = _ildc_w; break; default: assert(false); } break; case CONSTANT_Float: bc_which = bc_floatref; switch (bc) { case _ldc: vbc = _fldc; break; case _ldc_w: vbc = _fldc_w; break; default: assert(false); } break; case CONSTANT_Long: bc_which = bc_longref; assert(bc == _ldc2_w); vbc = _lldc2_w; break; case CONSTANT_Double: bc_which = bc_doubleref; assert(bc == _ldc2_w); vbc = _dldc2_w; break; case CONSTANT_String: bc_which = bc_stringref; switch (bc) { case _ldc: vbc = _sldc; break; case _ldc_w: vbc = _sldc_w; break; default: assert(false); } break; case CONSTANT_Class: bc_which = bc_classref; switch (bc) { case _ldc: vbc = _cldc; break; case _ldc_w: vbc = _cldc_w; break; default: assert(false); } break; default: // CONSTANT_MethodHandle, etc. if (getHighestClassVersion().lessThan(JAVA7_MAX_CLASS_VERSION)) { throw new IOException("bad class file major version for Java 7 ldc"); } bc_which = bc_loadablevalueref; switch (bc) { case _ldc: vbc = _qldc; break; case _ldc_w: vbc = _qldc_w; break; default: assert(false); } } break; case CONSTANT_Class: // Use a special shorthand for the current class: if (ref == curClass.thisClass) ref = null; bc_which = bc_classref; break; case CONSTANT_Fieldref: bc_which = bc_fieldref; break; case CONSTANT_Methodref: bc_which = bc_methodref; break; case CONSTANT_InterfaceMethodref: bc_which = bc_imethodref; break; case CONSTANT_InvokeDynamic: bc_which = bc_indyref; break; default: bc_which = null; assert(false); } bc_codes.putByte(vbc); bc_which.putRef(ref); // handle trailing junk if (bc == _multianewarray) { assert(i.getConstant() == code.getByte(i.getPC()+3)); // Just dump the byte into the bipush pile bc_byte.putByte(0xFF & i.getConstant()); } else if (bc == _invokeinterface) { assert(i.getLength() == 5); // Make sure the discarded bytes are sane: assert(i.getConstant() == (1+((MemberEntry)ref).descRef.typeRef.computeSize(true)) << 8); } else if (bc == _invokedynamic) { if (getHighestClassVersion().lessThan(JAVA7_MAX_CLASS_VERSION)) { throw new IOException("bad class major version for Java 7 invokedynamic"); } assert(i.getLength() == 5); assert(i.getConstant() == 0); // last 2 bytes MBZ } else { // Make sure there is nothing else to write. assert(i.getLength() == ((bc == _ldc)?2:3)); } continue; } int slot = i.getLocalSlot(); if (slot >= 0) { bc_codes.putByte(bc); bc_local.putInt(slot); int con = i.getConstant(); if (bc == _iinc) { if (!i.isWide()) { bc_byte.putByte(0xFF & con); } else { bc_short.putInt(0xFFFF & con); } } else { assert(con == 0); } continue; } // Generic instruction. Copy the body. bc_codes.putByte(bc); int pc = i.getPC()+1; int npc = i.getNextPC(); if (pc < npc) { // Do a few remaining multi-byte instructions. switch (bc) { case _sipush: bc_short.putInt(0xFFFF & i.getConstant()); break; case _bipush: bc_byte.putByte(0xFF & i.getConstant()); break; case _newarray: bc_byte.putByte(0xFF & i.getConstant()); break; default: assert(false); // that's it } } } bc_codes.putByte(_end_marker); bc_codes.elementCountForDebug++; codeHist[_end_marker]++; endCode(); } int[] codeHist = new int[1<<8]; int[] ldcHist = new int[20]; void printCodeHist() { assert(verbose > 0); String[] hist = new String[codeHist.length]; int totalBytes = 0; for (int bc = 0; bc < codeHist.length; bc++) { totalBytes += codeHist[bc]; } for (int bc = 0; bc < codeHist.length; bc++) { if (codeHist[bc] == 0) { hist[bc] = ""; continue; } String iname = Instruction.byteName(bc); String count = "" + codeHist[bc]; count = " ".substring(count.length()) + count; String pct = "" + (codeHist[bc] * 10000 / totalBytes); while (pct.length() < 4) { pct = "0" + pct; } pct = pct.substring(0, pct.length()-2) + "." + pct.substring(pct.length()-2); hist[bc] = count + " " + pct + "% " + iname; } Arrays.sort(hist); System.out.println("Bytecode histogram ["+totalBytes+"]"); for (int i = hist.length; --i >= 0; ) { if ("".equals(hist[i])) continue; System.out.println(hist[i]); } for (int tag = 0; tag < ldcHist.length; tag++) { int count = ldcHist[tag]; if (count == 0) continue; System.out.println("ldc "+ConstantPool.tagName(tag)+" "+count); } } }