blob: 2447fa45e8b03b60724e7944c0714da2c44e5c9a [file] [log] [blame]
/*
* 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().isEmpty());
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);
}
}
}