| /* |
| * 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.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<Entry> requiredEntries; // for the CP |
| Map<Attribute.Layout, int[]> backCountTable; // for layout callables |
| int[][] attrCounts; // count attr. occurrences |
| |
| 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 (51.0) without |
| // Indy is identical to jdk6 class file (50.0). |
| packageVersion = JAVA6_PACKAGE_VERSION; |
| } else if (highV.equals(JAVA7_MAX_CLASS_VERSION)) { |
| packageVersion = JAVA7_PACKAGE_VERSION; |
| } else { |
| // Normal case. Use the newest archive format, when available |
| packageVersion = JAVA8_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<Package.Version, int[]> 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<normalZipSize-minWin)); |
| if (specialZipSize < normalZipSize-minWin) { |
| IntBand big = cp_Utf8_big_chars.newIntBand(bandName); |
| big.initializeValues(cvals); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void writeSignatureBands(Entry[] cpMap) throws IOException { |
| for (int i = 0; i < cpMap.length; i++) { |
| SignatureEntry e = (SignatureEntry) cpMap[i]; |
| cp_Signature_form.putRef(e.formRef); |
| for (int j = 0; j < e.classRefs.length; j++) { |
| cp_Signature_classes.putRef(e.classRefs[j]); |
| } |
| } |
| } |
| |
| void writeMemberRefs(byte tag, Entry[] cpMap, CPRefBand cp_class, CPRefBand cp_desc) throws IOException { |
| for (int i = 0; i < cpMap.length; i++) { |
| MemberEntry e = (MemberEntry) cpMap[i]; |
| cp_class.putRef(e.classRef); |
| cp_desc.putRef(e.descRef); |
| } |
| } |
| |
| void writeFiles() throws IOException { |
| int numFiles = pkg.files.size(); |
| if (numFiles == 0) return; |
| int options = archiveOptions; |
| boolean haveSizeHi = testBit(options, AO_HAVE_FILE_SIZE_HI); |
| boolean haveModtime = testBit(options, AO_HAVE_FILE_MODTIME); |
| boolean haveOptions = testBit(options, AO_HAVE_FILE_OPTIONS); |
| if (!haveOptions) { |
| for (File file : pkg.files) { |
| if (file.isClassStub()) { |
| haveOptions = true; |
| options |= AO_HAVE_FILE_OPTIONS; |
| archiveOptions = options; |
| break; |
| } |
| } |
| } |
| if (haveSizeHi || haveModtime || haveOptions || !pkg.files.isEmpty()) { |
| options |= AO_HAVE_FILE_HEADERS; |
| archiveOptions = options; |
| } |
| for (File file : pkg.files) { |
| file_name.putRef(file.name); |
| long len = file.getFileLength(); |
| file_size_lo.putInt((int)len); |
| if (haveSizeHi) |
| file_size_hi.putInt((int)(len >>> 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<<attrIndexLimit[i])-1; |
| int nextLoBit = 0; |
| Map<Attribute.Layout, int[]> defMap = allLayouts.get(i); |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| Map.Entry<Attribute.Layout, int[]>[] 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<Attribute.Layout, int[]> e0, |
| Map.Entry<Attribute.Layout, int[]> 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<Attribute.Layout, int[]> 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<Map<Attribute.Layout, int[]>> 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<Attribute.Layout, int[]> 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<Object[]> 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<<j)) { |
| // either undefined or predefined; nothing to write |
| continue; |
| } |
| } |
| Attribute.Layout def = attrDefs.get(i).get(j); |
| defList.add(new Object[]{ Integer.valueOf(header), def }); |
| assert(Integer.valueOf(j).equals(attrIndexTable.get(def))); |
| } |
| } |
| // Sort the new attr defs into some "natural" order. |
| int numAttrDefs = defList.size(); |
| Object[][] defs = new Object[numAttrDefs][]; |
| defList.toArray(defs); |
| Arrays.sort(defs, new Comparator<>() { |
| 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<ClassEntry, InnerClass> 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<InnerClass> 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<Class.Field> 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<Class.Method> 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<<index)) { |
| if (verbose > 3) |
| Utils.log.fine("Adding flag bit 1<<"+index+" in "+Long.toHexString(flagMask)); |
| assert(!testBit(h.flags, 1L<<index)); |
| flagsToAdd |= (1L<<index); |
| flagMask -= (1L<<index); // do not use this bit twice here |
| } else { |
| // an overflow attr. |
| flagsToAdd |= (1L<<X_ATTR_OVERFLOW); |
| overflowCount += 1; |
| if (verbose > 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 ("<init>".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); |
| // do not optimize this case, simply fall back to regular coding |
| if ((bc == _invokespecial || bc == _invokestatic) && |
| ref.tagEquals(CONSTANT_InterfaceMethodref)) |
| return -1; |
| 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 <init> 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 <init> 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: |
| if (ref.tagEquals(CONSTANT_InterfaceMethodref)) { |
| if (bc == _invokespecial) |
| vbc = _invokespecial_int; |
| if (bc == _invokestatic) |
| vbc = _invokestatic_int; |
| bc_which = bc_imethodref; |
| } else { |
| 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); |
| } |
| if (ref != null && bc_which.index != null && !bc_which.index.contains(ref)) { |
| // Crash and burn with a complaint if there are funny |
| // references for this bytecode instruction. |
| // Example: invokestatic of a CONSTANT_InterfaceMethodref. |
| String complaint = code.getMethod() + |
| " contains a bytecode " + i + |
| " with an unsupported constant reference; please use the pass-file option on this class."; |
| Utils.log.warning(complaint); |
| throw new IOException(complaint); |
| } |
| 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); |
| } |
| } |
| } |