diff --git a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/util/test/ObjectCopierTest.java b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/util/test/ObjectCopierTest.java index 98b6513eafbb..706305ef9591 100644 --- a/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/util/test/ObjectCopierTest.java +++ b/compiler/src/jdk.graal.compiler.test/src/jdk/graal/compiler/util/test/ObjectCopierTest.java @@ -25,6 +25,7 @@ package jdk.graal.compiler.util.test; import java.io.IOException; +import java.io.PrintStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; @@ -175,20 +176,14 @@ public void testIt() { List externalValueFields = List.of(ObjectCopier.getField(BaseClass.class, "BASE_SINGLETON"), ObjectCopier.getField(TestObject.class, "TEST_OBJECT_SINGLETON")); - String encoded = ObjectCopier.encode(new ObjectCopier.Encoder(externalValueFields), root); - if (DEBUG) { - System.out.printf("encoded:%n%s%n", encoded); - } + byte[] encoded = encode(externalValueFields, root, "encoded"); Object decoded = ObjectCopier.decode(encoded, loader); if (DEBUG) { System.out.printf("root:%n%s%n", root); System.out.printf("decoded:%n%s%n", decoded); } - String reencoded = ObjectCopier.encode(new ObjectCopier.Encoder(externalValueFields), decoded); - if (DEBUG) { - System.out.printf("reencoded:%n%s%n", reencoded); - } - Assert.assertEquals(encoded, reencoded); + byte[] reencoded = encode(externalValueFields, decoded, "reencoded"); + Assert.assertArrayEquals(encoded, reencoded); Map root2 = (Map) ObjectCopier.decode(reencoded, loader); @@ -201,6 +196,15 @@ public void testIt() { Assert.assertSame(root2.get("singleton2"), root2.get("singleton2_2")); } + private static byte[] encode(List externalValueFields, Object root, String debugLabel) { + PrintStream debugStream = null; + if (DEBUG) { + debugStream = System.out; + debugStream.printf("%s:%n", debugLabel); + } + return ObjectCopier.encode(new ObjectCopier.Encoder(externalValueFields, debugStream), root); + } + @Test public void test() throws IOException, InterruptedException { launchSubprocess(this::testIt, diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/BuildTime.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/BuildTime.java index 125e6f54bbfa..4d23c3ac3989 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/BuildTime.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/BuildTime.java @@ -39,14 +39,14 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import jdk.graal.compiler.core.ArchitectureSpecific; -import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; import org.graalvm.collections.EconomicMap; +import jdk.graal.compiler.core.ArchitectureSpecific; import jdk.graal.compiler.core.common.spi.ForeignCallSignature; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.debug.TTY; import jdk.graal.compiler.graph.NodeClass; +import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; import jdk.graal.compiler.hotspot.EncodedSnippets; import jdk.graal.compiler.hotspot.HotSpotForeignCallLinkage; import jdk.graal.compiler.hotspot.HotSpotReplacementsImpl; @@ -148,7 +148,7 @@ public static void configureGraalForLibGraal(String arch, Consumer> registerAsInHeap, Consumer>> hostedGraalSetFoldNodePluginClasses, String nativeImageLocationQualifier, - String encodedGuestObjects) { + byte[] encodedGuestObjects) { GraalError.guarantee(VALID_LOADER_NAME.equals(LOADER.getName()), "Only call this method from classloader " + VALID_LOADER_NAME); diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/CompilerConfig.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/CompilerConfig.java index 28e61f20f7ea..70bae2af222e 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/CompilerConfig.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/libgraal/CompilerConfig.java @@ -93,9 +93,9 @@ protected ClassInfo makeClassInfo(Class declaringClass) { return ci; } }; - String encoded = ObjectCopier.encode(encoder, encodedObjects); + byte[] encoded = ObjectCopier.encode(encoder, encodedObjects); - Files.writeString(Path.of(args[0]), encoded); + Files.write(Path.of(args[0]), encoded); } private static EncodedSnippets getEncodedSnippets(HotSpotReplacementsImpl replacements, OptionValues options) { diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java index 4be038275550..2838cd3298a3 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopier.java @@ -24,6 +24,7 @@ */ package jdk.graal.compiler.util; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; @@ -40,17 +41,15 @@ import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.graalvm.collections.EconomicMap; @@ -61,41 +60,21 @@ import org.graalvm.word.LocationIdentity; import jdk.graal.compiler.core.common.FieldIntrospection; +import jdk.graal.compiler.core.common.util.FrequencyEncoder; import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.replacements.SnippetTemplate; import jdk.internal.misc.Unsafe; /** - * Support for deep copying an object across processes by {@linkplain #encode encoding} it to a - * String in the first process and {@linkplain #decode decoding} it back into an object in the - * second process. This copying requires that the classes of the copied objects are the same in both + * Support for deep copying an object across processes by {@linkplain #encode encoding} it to bytes + * in the first process and {@linkplain #decode decoding} it back into an object in the second + * process. This copying requires that the classes of the copied objects are the same in both * processes with respect to fields. * - * Encoded format in EBNF: - * - *
- *  enc = line "\n" { line "\n" }
- *  line = header | objectField
- *  header = id ":" ( builtin | object | array | fieldRef )
- *  object = "{" className ":" fieldCount "}"
- *  objectField = "  " fieldName ":" id " = " fieldValue
- *  fieldValue = ( primitive | id )
- *  id = int
- *  array = "[" className "] = " elements
- *  elements = [ fieldValue { " " fieldValue } ]
- *  fieldRef = "@" className "." fieldName
- *  builtin = "<"  className [ ":" encodingName ] "> = " builtinValue
- * 
- * - * See the {@link Builtin} subclasses for the EBNF of builtinValue. + * See the {@link Builtin} subclasses for encoding of specific types. */ public class ObjectCopier { - private static final Pattern BUILTIN_LINE = Pattern.compile("<(?[^:}]+)(?::(?\\w+))?> = (?.*)"); - private static final Pattern OBJECT_LINE = Pattern.compile("\\{(?[\\w.$]+):(?\\d+)}"); - private static final Pattern ARRAY_LINE = Pattern.compile("\\[(?[^]]+)] = (?.*)"); - private static final Pattern FIELD_LINE = Pattern.compile("\\s*(?[^:]+):(?[^ ]+) = (?.*)"); - /** * A builtin is specialized support for encoded and decoding values of specific types. */ @@ -134,36 +113,23 @@ final void checkClass(Class c) { } /** - * Gets the name of a non-default encoded used by this builtin for {@code obj}. - * - * @return null if the default encoded is used for {@code obj} - */ - @SuppressWarnings("unused") - String encodingName(Object obj) { - return null; - } - - /** - * Ensures object ids have are created for the values referenced by {@code obj} that will be + * Ensures object ids are created for the values referenced by {@code obj} that will be * handled by this builtin when {@code obj} is encoded. For example, the values in a map. */ @SuppressWarnings("unused") - void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + protected void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { } /** * Encodes the value of {@code obj} to a String that does not contain {@code '\n'} or * {@code '\r'}. */ - protected abstract String encode(Encoder encoder, Object obj); + protected abstract void encode(Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException; /** * Decodes {@code encoded} to an object of a type handled by this builtin. - * - * @param encoding the non-default encoded used when encoded the object or null if the - * default encoded was used */ - protected abstract Object decode(Decoder decoder, Class concreteType, String encoding, String encoded); + protected abstract Object decode(Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException; @Override public String toString() { @@ -172,15 +138,8 @@ public String toString() { } /** - * Builtin for handling {@link Class} values. - * - * EBNF: - * - *
-     * builtinValue = className
-     * 
- * - * The className is in {@link Class#getName()} format. + * Builtin for handling {@link Class} values. The className is in {@link Class#getName()} + * format. */ static final class ClassBuiltin extends Builtin { @@ -188,13 +147,24 @@ static final class ClassBuiltin extends Builtin { super(Class.class); } - @Override - protected String encode(Encoder encoder, Object obj) { + private static String getName(Object obj) { return ((Class) obj).getName(); } @Override - protected Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + protected void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + encoder.makeStringId(getName(obj), objectPath); + } + + @Override + protected void encode(Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + String name = getName(obj); + encoder.writeString(stream, name); + } + + @Override + protected Object decode(Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + String encoded = decoder.readString(stream); return switch (encoded) { case "boolean" -> boolean.class; case "byte" -> byte.class; @@ -212,14 +182,6 @@ protected Object decode(Decoder decoder, Class concreteType, String encoding, /** * Builtin for handling {@link String} values. - * - * EBNF: - * - *
-     * builtinValue = string
-     * 
- * - * The string has no embedded \r or \n characters. */ static final class StringBuiltin extends Builtin { @@ -227,31 +189,23 @@ static final class StringBuiltin extends Builtin { super(String.class, char[].class); } + private static String asString(Object obj) { + return obj instanceof String ? (String) obj : new String((char[]) obj); + } + @Override - String encodingName(Object obj) { - String s = obj instanceof String ? (String) obj : new String((char[]) obj); - if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) { - return "escaped"; - } - return super.encodingName(obj); + protected void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + encoder.makeStringId(asString(obj), objectPath); } @Override - protected String encode(Encoder encoder, Object obj) { - String s = obj instanceof String ? (String) obj : new String((char[]) obj); - if ("escaped".equals(encodingName(s))) { - return s.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r"); - } - return s; + protected void encode(Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + encoder.writeString(stream, asString(obj)); } @Override - protected Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { - String s = encoded; - if (encoding != null) { - GraalError.guarantee(encoding.equals("escaped"), "Unknown encoded: %s", encoding); - s = encoded.replace("\\r", "\r").replace("\\n", "\n").replace("\\\\", "\\"); - } + protected Object decode(Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + String s = decoder.readString(stream); if (concreteType == char[].class) { return s.toCharArray(); } @@ -260,15 +214,8 @@ protected Object decode(Decoder decoder, Class concreteType, String encoding, } /** - * Builtin for handling {@link Enum} values. - * - * EBNF: - * - *
-     * builtinValue = enumName
-     * 
- * - * The enumName is given by {@link Enum#name()}. + * Builtin for handling {@link Enum} values. The value is described by its + * {@link Enum#ordinal()}. */ static final class EnumBuiltin extends Builtin { @@ -277,28 +224,27 @@ static final class EnumBuiltin extends Builtin { } @Override - protected String encode(Encoder encoder, Object obj) { - return ((Enum) obj).name(); + protected void encode(Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + Enum con = (Enum) obj; + stream.writePackedUnsignedInt(con.ordinal()); + stream.writeShort(fingerprint(con)); + } + + private static short fingerprint(Enum con) { + int h = con.name().hashCode(); + return hashIntToShort(h); } - @SuppressWarnings({"unchecked", "rawtypes"}) @Override - protected Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { - return Enum.valueOf((Class) concreteType, encoded); + protected Object decode(Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int ord = stream.readPackedUnsignedInt(); + int fingerprint = stream.readShort(); + Enum con = (Enum) concreteType.getEnumConstants()[ord]; + GraalError.guarantee(fingerprint(con) == fingerprint, "Enum constant type mismatch: %s ordinal %d not expected to be %s", concreteType.getName(), ord, con); + return con; } } - /** - * Builtin for handling {@link HashMap} or {@link IdentityHashMap} values. - * - * EBNF: - * - *
-     *  builtinValue = [ key ":" value { " " } key ":" value ]
-     *  key = fieldValue
-     *  value = fieldValue
-     * 
- */ static final class HashMapBuiltin extends Builtin { final Map, Supplier> factories; @@ -314,44 +260,33 @@ static final class HashMapBuiltin extends Builtin { } @Override - void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + protected void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { Map map = (Map) obj; encoder.makeMapChildIds(new EconomicMapWrap<>(map), objectPath); } @Override - protected String encode(Encoder encoder, Object obj) { + protected void encode(Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { Map map = (Map) obj; - return encoder.encodeMap(new EconomicMapWrap<>(map)); + encoder.encodeMap(stream, new EconomicMapWrap<>(map)); } @SuppressWarnings("unchecked") @Override - protected Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + protected Object decode(Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { Map map = (Map) factories.get(concreteType).get(); - decoder.decodeMap(encoded, map::put); + decoder.decodeMap(stream, map::put); return map; } } - /** - * Builtin for handling {@link EconomicMap} values. - * - * EBNF: - * - *
-     *  builtinValue = [ key ":" value { " " } key ":" value ]
-     *  key = fieldValue
-     *  value = fieldValue
-     * 
- */ static final class EconomicMapBuiltin extends Builtin { EconomicMapBuiltin() { super(EconomicMap.class, EconomicMap.create().getClass()); } @Override - void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { + protected void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { EconomicMap map = (EconomicMap) obj; GraalError.guarantee(map.getEquivalenceStrategy() == Equivalence.DEFAULT, "Only DEFAULT strategy supported: %s", map.getEquivalenceStrategy()); @@ -359,15 +294,15 @@ void makeChildIds(Encoder encoder, Object obj, ObjectPath objectPath) { } @Override - protected String encode(Encoder encoder, Object obj) { - return encoder.encodeMap((UnmodifiableEconomicMap) obj); + protected void encode(Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + encoder.encodeMap(stream, (UnmodifiableEconomicMap) obj); } @Override - protected Object decode(Decoder decoder, Class concreteType, String encoding, String encoded) { + protected Object decode(Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { if (EconomicMap.class.isAssignableFrom(concreteType)) { EconomicMap map = EconomicMap.create(); - decoder.decodeMap(encoded, map::put); + decoder.decodeMap(stream, map::put); return map; } else { throw new GraalError("Unexpected concrete Map type: ", concreteType); @@ -383,9 +318,9 @@ protected Object decode(Decoder decoder, Class concreteType, String encoding, * have the same name in which case the descriptor includes the qualified name of the * class declaring the field as a prefix. */ - public record ClassInfo(Class clazz, Map fields) { + public record ClassInfo(Class clazz, SortedMap fields, short fingerprint) { public static ClassInfo of(Class declaringClass) { - Map fields = new HashMap<>(); + SortedMap fields = new TreeMap<>(); for (Class c = declaringClass; !c.equals(Object.class); c = c.getSuperclass()) { for (Field f : c.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers())) { @@ -399,10 +334,19 @@ public static ClassInfo of(Class declaringClass) { } } } - return new ClassInfo(declaringClass, fields); + int h = fields.size(); + for (var entry : fields.entrySet()) { + h = h * 31 + entry.getKey().hashCode(); + h = h * 31 + entry.getValue().getType().getName().hashCode(); + } + return new ClassInfo(declaringClass, fields, hashIntToShort(h)); } } + private static short hashIntToShort(int h) { + return (short) (h ^ (h >>> 16)); + } + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); final Map, ClassInfo> classInfos = new HashMap<>(); @@ -437,32 +381,26 @@ public ObjectCopier() { addBuiltin(stringBuiltin, char[].class); } - static String[] splitSpaceSeparatedElements(String elements) { - if (elements.isEmpty()) { - return new String[0]; - } - return elements.split(" "); - } - /** * Encodes {@code root} to a String using {@code encoder}. */ - public static String encode(Encoder encoder, Object root) { - int rootId = encoder.makeId(root, ObjectPath.of("[root:" + root.getClass().getName() + "]")).id(); - GraalError.guarantee(rootId == 1, "The root object should have id of 1, not %d", rootId); + public static byte[] encode(Encoder encoder, Object root) { + encoder.makeId(root, ObjectPath.of("[root:" + root.getClass().getName() + "]")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try (PrintStream ps = new PrintStream(baos)) { - encoder.encode(ps); + try (ObjectCopierOutputStream cos = new ObjectCopierOutputStream(baos, encoder.debugOutput)) { + encoder.encode(cos, root); + } catch (IOException e) { + throw new RuntimeException(e); } - return baos.toString(); + return baos.toByteArray(); } - public static Object decode(String encoded, ClassLoader loader) { + public static Object decode(byte[] encoded, ClassLoader loader) { Decoder decoder = new Decoder(loader); return decode(decoder, encoded); } - public static Object decode(Decoder decoder, String encoded) { + public static Object decode(Decoder decoder, byte[] encoded) { return decoder.decode(encoded); } @@ -489,11 +427,12 @@ Object getObject(int id, boolean requireNonNull) { return obj; } - void decodeMap(String encoded, BiConsumer putMethod) { - for (String e : splitSpaceSeparatedElements(encoded)) { - String[] keyValue = e.split(":"); - GraalError.guarantee(keyValue.length == 2, "Invalid encoded key:value: %s", e); - resolveId(keyValue[0], k -> resolveId(keyValue[1], v -> putMethod.accept(k, v))); + void decodeMap(ObjectCopierInputStream stream, BiConsumer putMethod) throws IOException { + int size = stream.readPackedUnsignedInt(); + for (int i = 0; i < size; i++) { + int keyId = readId(stream); + int valueId = readId(stream); + resolveId(keyId, k -> resolveId(valueId, v -> putMethod.accept(k, v))); } } @@ -513,221 +452,117 @@ private static void writeField(Field field, Object receiver, Object value) { /** * Action deferred due to unresolved object id. */ - record Deferred(Runnable runnable, int lineNum) { + record Deferred(Runnable runnable, int recordNum, int fieldNum) { } List deferred; - int lineNum = -1; + int recordNum = -1; + int fieldNum = -1; + String[] strings; - private Object decode(String encoded) { + private Object decode(byte[] encoded) { + int rootId; deferred = new ArrayList<>(); - lineNum = 0; + recordNum = 0; - Iterator iter = encoded.lines().iterator(); - try { - while (iter.hasNext()) { - String line = iter.next(); - lineNum++; - int colon = line.indexOf(':'); - GraalError.guarantee(colon != -1, "Missing ':' in line: %s", line); - int id = Integer.parseInt(line.substring(0, colon)); - switch (line.charAt(colon + 1)) { + try (ObjectCopierInputStream stream = new ObjectCopierInputStream(new ByteArrayInputStream(encoded))) { + int nstrings = stream.readPackedUnsignedInt(); + strings = new String[nstrings]; + for (int i = 0; i < nstrings; i++) { + strings[i] = stream.readStringValue(); + } + rootId = readId(stream); + for (int id = 1;; id++) { + recordNum = id; + fieldNum = -1; + int c = stream.read(); + if (c == -1) { + break; + } + switch (c) { case '<': { - Matcher matcher = BUILTIN_LINE.matcher(line.substring(colon + 1)); - GraalError.guarantee(matcher.matches(), "Invalid builtin line: %s", line); - String className = matcher.group("class"); - String encodingName = matcher.group("encodingName"); - String value = matcher.group("value"); + String className = readString(stream); Class clazz = loadClass(className); Builtin builtin = getBuiltin(clazz); - GraalError.guarantee(builtin != null, "No builtin for %s: %s", className, line); + GraalError.guarantee(builtin != null, "No builtin for %s in record %d", className, recordNum); builtin.checkClass(clazz); - addDecodedObject(id, builtin.decode(this, clazz, encodingName, value)); + addDecodedObject(id, builtin.decode(this, clazz, stream)); break; } - case '[': { - Matcher matcher = ARRAY_LINE.matcher(line.substring(colon + 1)); - GraalError.guarantee(matcher.matches(), "Invalid array line: %s", line); - String componentTypeName = matcher.group("componentType"); - String[] elements = splitSpaceSeparatedElements(matcher.group("elements")); - switch (componentTypeName) { - case "boolean": { - boolean[] arr = new boolean[elements.length]; - for (int i = 0; i < elements.length; i++) { - arr[i] = Boolean.parseBoolean(elements[i]); - } - addDecodedObject(id, arr); - break; - } - case "byte": { - byte[] arr = new byte[elements.length]; - for (int i = 0; i < elements.length; i++) { - arr[i] = Byte.parseByte(elements[i]); - } - addDecodedObject(id, arr); - break; - } - case "char": { - throw GraalError.shouldNotReachHere("char[] should be handled by " + StringBuiltin.class); - } - case "short": { - short[] arr = new short[elements.length]; - for (int i = 0; i < elements.length; i++) { - arr[i] = Short.parseShort(elements[i]); - } - addDecodedObject(id, arr); - break; - } - case "int": { - int[] arr = new int[elements.length]; - for (int i = 0; i < elements.length; i++) { - arr[i] = Integer.parseInt(elements[i]); - } - addDecodedObject(id, arr); - break; - } - case "float": { - float[] arr = new float[elements.length]; - for (int i = 0; i < elements.length; i++) { - arr[i] = Float.parseFloat(elements[i]); - } - addDecodedObject(id, arr); - break; - } - case "long": { - long[] arr = new long[elements.length]; - for (int i = 0; i < elements.length; i++) { - arr[i] = Long.parseLong(elements[i]); - } - addDecodedObject(id, arr); - break; - } - case "double": { - double[] arr = new double[elements.length]; - for (int i = 0; i < elements.length; i++) { - arr[i] = Double.parseDouble(elements[i]); - } - addDecodedObject(id, arr); - break; - } - default: { - Class componentType = loadClass(componentTypeName); - Object[] arr = (Object[]) Array.newInstance(componentType, elements.length); - addDecodedObject(id, arr); - for (int i = 0; i < elements.length; i++) { - int elementId = Integer.parseInt(elements[i]); - int index = i; - resolveId(elementId, o -> arr[index] = o); - } - break; - } + case '[': { // primitive array + Object arr = stream.readTypedPrimitiveArray(); + addDecodedObject(id, arr); + break; + } + case ']': { // object array + String componentTypeName = readString(stream); + Class componentType = loadClass(componentTypeName); + int length = stream.readPackedUnsignedInt(); + int[] elements = new int[length]; + for (int i = 0; i < length; i++) { + elements[i] = readId(stream); + } + Object[] arr = (Object[]) Array.newInstance(componentType, elements.length); + addDecodedObject(id, arr); + for (int i = 0; i < elements.length; i++) { + int index = i; + resolveId(elements[i], o -> arr[index] = o); } break; } case '@': { - String fieldDesc = line.substring(colon + 2); - int lastDot = fieldDesc.lastIndexOf('.'); - GraalError.guarantee(lastDot != -1, "Invalid field name: %s", fieldDesc); - String className = fieldDesc.substring(0, lastDot); - String fieldName = fieldDesc.substring(lastDot + 1); + String className = readString(stream); + String fieldName = readString(stream); Class declaringClass = loadClass(className); Field field = getField(declaringClass, fieldName); addDecodedObject(id, readField(field, null)); break; } case '{': { - Matcher matcher = OBJECT_LINE.matcher(line.substring(colon + 1)); - GraalError.guarantee(matcher.matches(), "Invalid object line: %s", line); - String className = matcher.group("class"); - int fieldCount = Integer.parseInt(matcher.group("fieldCount")); + String className = readString(stream); Class clazz = loadClass(className); Object obj = allocateInstance(clazz); addDecodedObject(id, obj); ClassInfo classInfo = classInfos.computeIfAbsent(clazz, ClassInfo::of); - for (int i = 0; i < fieldCount; i++) { - GraalError.guarantee(iter.hasNext(), "Truncated input"); - String fieldLine = iter.next(); - lineNum++; - Matcher fieldMatcher = FIELD_LINE.matcher(fieldLine); - GraalError.guarantee(fieldMatcher.matches(), "Invalid field line: %s", fieldLine); - String fieldDesc = fieldMatcher.group("desc"); - String value = fieldMatcher.group("value"); - Field field = classInfo.fields().get(fieldDesc); - GraalError.guarantee(field != null, "Unknown field: %s", fieldDesc); + short fingerprint = stream.readShort(); + GraalError.guarantee(fingerprint == classInfo.fingerprint, "Type mismatch on %s", clazz); + fieldNum = 0; + for (Field field : classInfo.fields.values()) { Class type = field.getType(); - - int expectTypeId = Integer.parseInt(fieldMatcher.group("typeId")); - resolveId(expectTypeId, o -> checkFieldType(expectTypeId, field)); - if (type.isPrimitive()) { - switch (type.getName()) { - case "boolean": { - writeField(field, obj, Boolean.parseBoolean(value)); - break; - } - case "byte": { - writeField(field, obj, Byte.parseByte(value)); - break; - } - case "char": { - writeField(field, obj, (char) Integer.parseInt(value)); - break; - } - case "short": { - writeField(field, obj, Short.parseShort(value)); - break; - } - case "int": { - writeField(field, obj, Integer.parseInt(value)); - break; - } - case "float": { - writeField(field, obj, Float.parseFloat(value)); - break; - } - case "long": { - writeField(field, obj, Long.parseLong(value)); - break; - } - case "double": { - writeField(field, obj, Double.parseDouble(value)); - break; - } - default: { - throw new GraalError("Unexpected primitive type: %s", type.getName()); - } - } + char typeCh = type.descriptorString().charAt(0); + Object value = stream.readUntypedValue(typeCh); + writeField(field, obj, value); } else { + int value = readId(stream); resolveId(value, o -> writeField(field, obj, o)); } + fieldNum++; } break; } default: { - throw new GraalError("Invalid char after ':' in line: %s", line); + throw new GraalError("Invalid char '%c' for kind in record %d", c, recordNum); } } } for (Deferred d : deferred) { - lineNum = d.lineNum(); + recordNum = d.recordNum(); + fieldNum = d.fieldNum; d.runnable().run(); } } catch (Throwable e) { - String line = encoded.lines().skip(lineNum - 1).findFirst().get(); - throw new GraalError(e, "Error on line %d: %s", lineNum, line); + throw new GraalError(e, "Error in record %d (field %d)", recordNum, fieldNum); } finally { deferred = null; - lineNum = -1; + recordNum = -1; + fieldNum = -1; } - return getObject(1, true); + return getObject(rootId, true); } - void resolveId(String id, Consumer c) { - try { - resolveId(Integer.parseInt(id), c); - } catch (NumberFormatException e) { - throw new GraalError(e, "Invalid object id: %s", id); - } + private static int readId(ObjectCopierInputStream stream) throws IOException { + return stream.readPackedUnsignedInt(); } void resolveId(int id, Consumer c) { @@ -736,17 +571,16 @@ void resolveId(int id, Consumer c) { if (objValue != null) { c.accept(objValue); } else { - deferred.add(new Deferred(() -> c.accept(getObject(id, true)), lineNum)); + deferred.add(new Deferred(() -> c.accept(getObject(id, true)), recordNum, fieldNum)); } } else { c.accept(null); } } - private void checkFieldType(int expectTypeId, Field field) { - Class actualType = field.getType(); - Class expectType = (Class) idToObject.get(expectTypeId); - GraalError.guarantee(actualType.equals(expectType), "Type of %s has changed: %s != %s", field, expectType, actualType); + public String readString(ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return strings[id]; } private static Object allocateInstance(Class clazz) { @@ -787,8 +621,14 @@ final Builtin getBuiltin(Class clazz, boolean onlyCheck) { public static class Encoder extends ObjectCopier { - final Map objectToId = new IdentityHashMap<>(); - final List objects = new ArrayList<>(); + final FrequencyEncoder objects = FrequencyEncoder.createIdentityEncoder(); + + /** + * We use a separate string table to deduplicate strings and to make sure they are available + * upfront during decoding so that deferred processing with transitive dependencies is not + * needed. + */ + final FrequencyEncoder strings = FrequencyEncoder.createEqualityEncoder(); /** * Map from values to static final fields. In a serialized object graph, references to such @@ -797,17 +637,27 @@ public static class Encoder extends ObjectCopier { */ final Map externalValues; + private final PrintStream debugOutput; + public Encoder(List externalValueFields) { - this(gatherExternalValues(externalValueFields)); + this(externalValueFields, null); + } + + public Encoder(List externalValueFields, PrintStream debugOutput) { + this(gatherExternalValues(externalValueFields), debugOutput); } /** * Use precomputed {@code externalValues} to avoid recomputing them. */ public Encoder(Map externalValues) { - objects.add(null); - objectToId.put(null, new ObjectID(0, null)); + this(externalValues, null); + } + + public Encoder(Map externalValues, PrintStream debugOutput) { + objects.addObject(null); this.externalValues = externalValues; + this.debugOutput = debugOutput; } public static Map gatherExternalValues(List externalValueFields) { @@ -849,16 +699,16 @@ public Map getExternalValues() { return Collections.unmodifiableMap(externalValues); } - private String encodeMap(UnmodifiableEconomicMap map) { + private void encodeMap(ObjectCopierOutputStream stream, UnmodifiableEconomicMap map) throws IOException { + stream.internalWritePackedUnsignedInt(map.size()); + UnmodifiableMapCursor cursor = map.getEntries(); - StringBuilder value = new StringBuilder(); while (cursor.advance()) { - if (!value.isEmpty()) { - value.append(" "); - } - value.append(makeId(cursor.getKey(), null).id()).append(":").append(makeId(cursor.getValue(), null).id()); + debugf("%n "); + writeId(stream, getId(cursor.getKey())); + debugf(" :"); + writeId(stream, getId(cursor.getValue())); } - return value.toString(); } void makeMapChildIds(EconomicMap map, ObjectPath objectPath) { @@ -871,6 +721,19 @@ void makeMapChildIds(EconomicMap map, ObjectPath objectPath) { } } + public void writeString(ObjectCopierOutputStream stream, String s) throws IOException { + int id = strings.getIndex(s); + stream.internalWritePackedUnsignedInt(id); + if (debugOutput != null) { + debugf(" %s", escapeDebugStringValue(s)); + } + } + + public void makeStringId(String s, ObjectPath objectPath) { + GraalError.guarantee(s != null, "Illegal null string: Path %s", objectPath); + strings.addObject(s); + } + /** * Checks that {@code value} is not an instance of {@code type}. * @@ -883,58 +746,57 @@ static void checkIllegalValue(Class type, Object value, ObjectPath objectPath } } - ObjectID makeId(Object obj, ObjectPath objectPath) { - if (!objectToId.containsKey(obj)) { - ObjectID id = new ObjectID(objects.size(), objectPath); - Field field = externalValues.get(obj); - if (field != null) { - objects.add(field); - objectToId.put(obj, id); - objectToId.put(field, id); - return id; + void makeId(Object obj, ObjectPath objectPath) { + Field field = externalValues.get(obj); + if (field != null) { + if (objects.addObject(field)) { + makeStringId(field.getDeclaringClass().getName(), objectPath); + makeStringId(field.getName(), objectPath); } + return; + } - objects.add(obj); - objectToId.put(obj, id); - - Class clazz = obj.getClass(); - Builtin builtin = getBuiltin(clazz); - if (builtin != null) { - builtin.checkObject(obj); - builtin.makeChildIds(this, obj, objectPath); - return id; - } + if (!objects.addObject(obj)) { + return; // already known + } - checkIllegalValue(Field.class, obj, objectPath, "Field type is used in object copying implementation"); - checkIllegalValue(FieldIntrospection.class, obj, objectPath, "Graal metadata type cannot be copied"); + Class clazz = obj.getClass(); + Builtin builtin = getBuiltin(clazz); + if (builtin != null) { + builtin.checkObject(obj); + makeStringId(clazz.getName(), objectPath); + builtin.makeChildIds(this, obj, objectPath); + return; + } - if (clazz.isArray()) { - Class componentType = clazz.getComponentType(); - if (!componentType.isPrimitive()) { - Object[] objArray = (Object[]) obj; - int index = 0; - for (Object element : objArray) { - makeId(element, objectPath.add(index)); - index++; - } - } - } else { - checkIllegalValue(LocationIdentity.class, obj, objectPath, "must come from a static field"); - checkIllegalValue(HashSet.class, obj, objectPath, "hashes are typically not stable across VM executions"); - - ClassInfo classInfo = makeClassInfo(clazz, objectPath); - for (Field f : classInfo.fields().values()) { - String fieldName = f.getDeclaringClass().getSimpleName() + "#" + f.getName(); - makeId(f.getType(), objectPath.add(fieldName + ":type")); - if (!f.getType().isPrimitive()) { - Object fieldValue = readField(f, obj); - makeId(fieldValue, objectPath.add(fieldName)); - } + checkIllegalValue(Field.class, obj, objectPath, "Field type is used in object copying implementation"); + checkIllegalValue(FieldIntrospection.class, obj, objectPath, "Graal metadata type cannot be copied"); + + if (clazz.isArray()) { + Class componentType = clazz.getComponentType(); + if (!componentType.isPrimitive()) { + strings.addObject(componentType.getName()); + Object[] objArray = (Object[]) obj; + int index = 0; + for (Object element : objArray) { + makeId(element, objectPath.add(index)); + index++; } } - + } else { + checkIllegalValue(LocationIdentity.class, obj, objectPath, "must come from a static field"); + checkIllegalValue(HashSet.class, obj, objectPath, "hashes are typically not stable across VM executions"); + + makeStringId(clazz.getName(), objectPath); + ClassInfo classInfo = makeClassInfo(clazz, objectPath); + classInfo.fields().forEach((fieldDesc, f) -> { + String fieldName = f.getDeclaringClass().getSimpleName() + "#" + f.getName(); + if (!f.getType().isPrimitive()) { + Object fieldValue = readField(f, obj); + makeId(fieldValue, objectPath.add(fieldName)); + } + }); } - return objectToId.get(obj); } private ClassInfo makeClassInfo(Class clazz, ObjectPath objectPath) { @@ -945,55 +807,100 @@ private ClassInfo makeClassInfo(Class clazz, ObjectPath objectPath) { } } - private void encode(PrintStream out) { - for (int id = 1; id < objects.size(); id++) { - Object obj = objects.get(id); + private void encode(ObjectCopierOutputStream out, Object root) throws IOException { + String[] encodedStrings = strings.encodeAll(new String[strings.getLength()]); + out.internalWritePackedUnsignedInt(encodedStrings.length); + for (String s : encodedStrings) { + out.writeStringValue(s); + } + Object[] encodedObjects = objects.encodeAll(new Object[objects.getLength()]); + debugf("root:"); + writeId(out, getId(root)); + debugf("%n"); + for (int id = 1; id < encodedObjects.length; id++) { + Object obj = encodedObjects[id]; Class clazz = obj.getClass(); Builtin builtin = getBuiltin(clazz); if (builtin != null) { - String encodingName = builtin.encodingName(obj); - String encoding = encodingName == null ? "" : ":" + encodingName; - out.printf("%d:<%s%s> = %s%n", id, clazz.getName(), encoding, builtin.encode(this, obj)); + out.internalWriteByte('<'); + debugf("%d:<", id); + writeString(out, clazz.getName()); + debugf(" > ="); + try { + builtin.encode(this, out, obj); + } catch (IOException e) { + throw new RuntimeException(e); + } } else if (clazz.isArray()) { Class componentType = clazz.getComponentType(); if (!componentType.isPrimitive()) { - String elements = Stream.of((Object[]) obj).map(this::getIdString).collect(Collectors.joining(" ")); - out.printf("%d:[%s] = %s%n", id, componentType.getName(), elements); - } else { - int length = Array.getLength(obj); - StringBuilder elements = new StringBuilder(length * 5); - for (int i = 0; i < length; i++) { - elements.append(' ').append(Array.get(obj, i)); + out.internalWriteByte(']'); + debugf("%d:[", id); + writeString(out, componentType.getName()); + debugf(" ] ="); + Object[] objs = (Object[]) obj; + out.internalWritePackedUnsignedInt(objs.length); + for (Object o : objs) { + writeId(out, getId(o)); } - out.printf("%d:[%s] =%s%n", id, componentType.getName(), elements); + } else { + out.internalWriteByte('['); + debugf("%d:[ %s ] =", id, componentType.getName()); + out.writeTypedPrimitiveArray(obj); } + } else if (clazz == Field.class) { + Field field = (Field) obj; + out.internalWriteByte('@'); + debugf("%d:@", id); + writeString(out, field.getDeclaringClass().getName()); + writeString(out, field.getName()); } else { - if (clazz == Field.class) { - Field field = (Field) obj; - out.printf("%d:@%s.%s%n", id, field.getDeclaringClass().getName(), field.getName()); - } else { - ClassInfo classInfo = classInfos.get(clazz); - out.printf("%d:{%s:%d}%n", id, clazz.getName(), classInfo.fields().size()); - for (var e : classInfo.fields().entrySet()) { - Field f = e.getValue(); - Object fValue = readField(f, obj); - Class fieldType = f.getType(); - int fieldTypeId = makeId(fieldType, null).id(); - if (!fieldType.isPrimitive()) { - fValue = getIdString(fValue); - } else if (fieldType == char.class) { - fValue = (int) (Character) fValue; - } - out.printf(" %s:%d = %s%n", e.getKey(), fieldTypeId, fValue); + ClassInfo classInfo = classInfos.get(clazz); + out.internalWriteByte('{'); + debugf("%d:{", id); + writeString(out, clazz.getName()); + debugf(" }"); + out.writeShort(classInfo.fingerprint); + for (var e : classInfo.fields().entrySet()) { + Field f = e.getValue(); + debugf("%n "); + Class fieldType = f.getType(); + Object fValue = readField(f, obj); + if (fieldType.isPrimitive()) { + out.writeUntypedValue(fValue); + } else { + writeId(out, getId(fValue)); + } + debugf("\t # %s", f.getName()); + if (!fieldType.isPrimitive()) { + debugf(" (object)"); } } } + debugf("%n"); } } - private String getIdString(Object o) { - GraalError.guarantee(objectToId.containsKey(o), "Unknown object: %s", o); - return String.valueOf(objectToId.get(o).id()); + private static void writeId(ObjectCopierOutputStream out, int id) throws IOException { + out.writePackedUnsignedInt(id); + } + + private int getId(Object o) { + Field field = externalValues.get(o); + if (field != null) { + return objects.getIndex(field); + } + return objects.getIndex(o); + } + + private void debugf(String format, Object... args) { + if (debugOutput != null) { + debugOutput.printf(format, args); + } + } + + static String escapeDebugStringValue(String s) { + return s.replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r"); } } @@ -1101,14 +1008,14 @@ private static void addImmutableCollectionsFields(List fields) { * @param prefix the prefix path * @param name the last field or array index read in the path */ - record ObjectPath(ObjectPath prefix, Object name) { + public record ObjectPath(ObjectPath prefix, Object name) { /** * Creates an object path for a root object. * * @param rootName names a reference to the root object (e.g. "[root]" or the qualified name * of a static field) */ - public static ObjectPath of(String rootName) { + static ObjectPath of(String rootName) { return new ObjectPath(null, rootName); } @@ -1153,10 +1060,4 @@ public String toString() { return String.join(".", components.reversed()); } } - - /** - * A unique int id for an object as well as the path by which it was (first) reached. - */ - record ObjectID(int id, ObjectPath path) { - } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopierInputStream.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopierInputStream.java new file mode 100644 index 000000000000..5256aee057da --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopierInputStream.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2024, 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 jdk.graal.compiler.util; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.nio.charset.StandardCharsets; + +/** + * Delegates to, but does not subclass, {@link TypedDataInputStream} for symmetry with + * {@link ObjectCopierOutputStream}, see the reasoning there. Add methods such as {@link #readShort} + * as needed. + */ +public class ObjectCopierInputStream extends InputStream { + private final TypedDataInputStream in; + + public ObjectCopierInputStream(InputStream in) { + this.in = new TypedDataInputStream(in); + } + + @Override + public int read() throws IOException { + return in.read(); + } + + @Override + public void close() throws IOException { + in.close(); + } + + public short readShort() throws IOException { + return in.readShort(); + } + + protected Object readUntypedValue(int type) throws IOException { + return switch (type) { + case 'I' -> (int) readPackedSignedLong(); + case 'J' -> readPackedSignedLong(); + case 'U' -> readStringValue(); + default -> in.readUntypedValue(type); + }; + } + + protected String readStringValue() throws IOException { + int len = readPackedUnsignedInt(); + byte[] bytes = new byte[len]; + in.readFully(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } + + public Object readTypedPrimitiveArray() throws IOException { + int len = readPackedUnsignedInt(); + int type = in.readUnsignedByte(); + Object arr = switch (type) { + case 'Z' -> new boolean[len]; + case 'B' -> new byte[len]; + case 'S' -> new short[len]; + case 'C' -> new char[len]; + case 'I' -> new int[len]; + case 'J' -> new long[len]; + case 'F' -> new float[len]; + case 'D' -> new double[len]; + default -> throw new IOException("Unsupported type: " + Integer.toHexString(type)); + }; + for (int i = 0; i < len; i++) { + Array.set(arr, i, readUntypedValue(type)); + } + return arr; + } + + public long readPackedSignedLong() throws IOException { + return decodeSign(readPacked()); + } + + public int readPackedUnsignedInt() throws IOException { + return Math.toIntExact(readPacked()); + } + + private static long decodeSign(long value) { + return (value >>> 1) ^ -(value & 1); + } + + private long readPacked() throws IOException { + int b0 = in.readUnsignedByte(); + if (b0 < ObjectCopierOutputStream.NUM_LOW_CODES) { + return b0; + } + assert b0 >= ObjectCopierOutputStream.NUM_LOW_CODES : b0; + long sum = b0; + long shift = ObjectCopierOutputStream.HIGH_WORD_SHIFT; + for (int i = 2;; i++) { + long b = in.readUnsignedByte(); + sum += b << shift; + if (b < ObjectCopierOutputStream.NUM_LOW_CODES || i == ObjectCopierOutputStream.MAX_BYTES) { + return sum; + } + shift += ObjectCopierOutputStream.HIGH_WORD_SHIFT; + } + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopierOutputStream.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopierOutputStream.java new file mode 100644 index 000000000000..fb6e73ec40fe --- /dev/null +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/ObjectCopierOutputStream.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2024, 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 jdk.graal.compiler.util; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.nio.charset.StandardCharsets; + +import jdk.graal.compiler.core.common.calc.UnsignedMath; + +/** + * Wraps an {@link OutputStream} and writes binary data of certain kinds to it. Optionally prints a + * text representation to a separate stream for debugging. This delegates to, but does not subclass, + * {@link TypedDataOutputStream} because it cannot override final public methods such as + * {@link DataOutputStream#writeShort} to intercept them for debug printing. Add such methods such + * as {@link #writeShort} as needed. + */ +public class ObjectCopierOutputStream extends OutputStream { + // Constants for UNSIGNED5 coding of Pack200 + protected static final long HIGH_WORD_SHIFT = 6; + protected static final long NUM_HIGH_CODES = 1 << HIGH_WORD_SHIFT; // number of high codes (64) + protected static final long NUM_LOW_CODES = (1 << Byte.SIZE) - NUM_HIGH_CODES; + protected static final long MAX_BYTES = 11; + + private final TypedDataOutputStream out; + private final PrintStream debugOut; + + /** + * @param debugOut {@code null} or a stream to print a text representation of the written binary + * data to. This stream is never closed. + */ + public ObjectCopierOutputStream(OutputStream out, PrintStream debugOut) { + this.out = new TypedDataOutputStream(out); + this.debugOut = debugOut; + } + + @Override + public void write(int b) throws IOException { + internalWriteByte(b); + if (debugOut != null) { + debugOut.printf(" 0x%02x", b); + } + } + + protected void internalWriteByte(int v) throws IOException { + out.writeByte(v); + } + + @Override + public void close() throws IOException { + out.close(); + } + + public void writeShort(int v) throws IOException { + out.writeShort(v); + debugPrintValue(v); + } + + public void writeUntypedValue(Object value) throws IOException { + Class valueClz = value.getClass(); + if (valueClz == Boolean.class) { + out.writeBoolean((Boolean) value); + } else if (valueClz == Byte.class) { + internalWriteByte((Byte) value); + } else if (valueClz == Short.class) { + out.writeShort((Short) value); + } else if (valueClz == Character.class) { + out.writeChar((Character) value); + } else if (valueClz == Integer.class) { + internalWritePackedSigned((int) value); + } else if (valueClz == Long.class) { + internalWritePackedSigned((long) value); + } else if (valueClz == Float.class) { + out.writeFloat((Float) value); + } else if (valueClz == Double.class) { + out.writeDouble((Double) value); + } else if (valueClz == String.class) { + writeStringValue((String) value); + } else { + throw new IllegalArgumentException(String.format("Unsupported type: Value: %s, Value type: %s", value, valueClz)); + } + debugPrintValue(value); + } + + protected void debugPrintValue(Object value) { + if (debugOut != null) { + Object debugValue = switch (value) { + case String s -> ObjectCopier.Encoder.escapeDebugStringValue(s); + case Character c -> (int) c; + default -> value; + }; + debugOut.printf(" %s", debugValue); + } + } + + protected void writeStringValue(String value) throws IOException { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + internalWritePackedUnsignedInt(bytes.length); + out.write(bytes); + } + + public void writeTypedPrimitiveArray(Object value) throws IOException { + Class compClz = value.getClass().componentType(); + int length = Array.getLength(value); + internalWritePackedUnsignedInt(length); + if (compClz == boolean.class) { + internalWriteByte('Z'); + for (int i = 0; i < length; i++) { + out.writeBoolean(Array.getBoolean(value, i)); + } + } else if (compClz == byte.class) { + internalWriteByte('B'); + for (int i = 0; i < length; i++) { + internalWriteByte(Array.getByte(value, i)); + } + } else if (compClz == short.class) { + internalWriteByte('S'); + for (int i = 0; i < length; i++) { + out.writeShort(Array.getShort(value, i)); + } + } else if (compClz == char.class) { + internalWriteByte('C'); + for (int i = 0; i < length; i++) { + out.writeChar(Array.getChar(value, i)); + } + } else if (compClz == int.class) { + internalWriteByte('I'); + for (int i = 0; i < length; i++) { + internalWritePackedSigned(Array.getInt(value, i)); + } + } else if (compClz == long.class) { + internalWriteByte('J'); + for (int i = 0; i < length; i++) { + internalWritePackedSigned(Array.getLong(value, i)); + } + } else if (compClz == float.class) { + internalWriteByte('F'); + for (int i = 0; i < length; i++) { + out.writeFloat(Array.getFloat(value, i)); + } + } else if (compClz == double.class) { + internalWriteByte('D'); + for (int i = 0; i < length; i++) { + out.writeDouble(Array.getDouble(value, i)); + } + } else { + throw new IllegalArgumentException(String.format("Unsupported array: Value: %s, Value type: %s", value, value.getClass())); + } + if (debugOut != null) { + for (int i = 0; i < length; i++) { + debugPrintValue(Array.get(value, i)); + } + } + } + + private static long encodeSign(long value) { + return (value << 1) ^ (value >> 63); + } + + protected void internalWritePackedSigned(long value) throws IOException { + // this is a modified version of the SIGNED5 encoding from Pack200 + writePacked(encodeSign(value)); + } + + public void writePackedUnsignedInt(int value) throws IOException { + internalWritePackedUnsignedInt(value); + debugPrintValue(value); + } + + protected void internalWritePackedUnsignedInt(int value) throws IOException { + // this is a modified version of the UNSIGNED5 encoding from Pack200 + writePacked(value); + } + + private void writePacked(long value) throws IOException { + if (UnsignedMath.belowThan(value, NUM_LOW_CODES)) { + internalWriteByte((int) value); + return; + } + long sum = value; + for (int i = 1; UnsignedMath.aboveOrEqual(sum, NUM_LOW_CODES) && i < MAX_BYTES; i++) { + sum -= NUM_LOW_CODES; + long u1 = NUM_LOW_CODES + (sum & (NUM_HIGH_CODES - 1)); // this is a "high code" + sum >>>= HIGH_WORD_SHIFT; // extracted 6 bits + internalWriteByte((int) u1); + } + // remainder is either a "low code" or the last byte + assert sum == (sum & 0xFF) : "not a byte"; + internalWriteByte((int) (sum & 0xFF)); + } +} diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataInputStream.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataInputStream.java index a9db7dd3afd4..eb0c8f9a4a45 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataInputStream.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataInputStream.java @@ -45,42 +45,28 @@ public TypedDataInputStream(InputStream in) { * @exception IOException in case of an I/O error. */ public Object readTypedValue() throws IOException { - Object value; - final byte type = readByte(); - switch (type) { - case 'Z': - value = readBoolean(); - break; - case 'B': - value = readByte(); - break; - case 'S': - value = readShort(); - break; - case 'C': - value = readChar(); - break; - case 'I': - value = readInt(); - break; - case 'J': - value = readLong(); - break; - case 'F': - value = readFloat(); - break; - case 'D': - value = readDouble(); - break; - case 'U': - int len = readInt(); - byte[] bytes = new byte[len]; - readFully(bytes); - value = new String(bytes, StandardCharsets.UTF_8); - break; - default: - throw new IOException("Unsupported type: " + Integer.toHexString(type)); - } - return value; + return readUntypedValue(readUnsignedByte()); + } + + protected Object readUntypedValue(int type) throws IOException { + return switch (type) { + case 'Z' -> readBoolean(); + case 'B' -> readByte(); + case 'S' -> readShort(); + case 'C' -> readChar(); + case 'I' -> readInt(); + case 'J' -> readLong(); + case 'F' -> readFloat(); + case 'D' -> readDouble(); + case 'U' -> readStringValue(); + default -> throw new IOException("Unsupported type: " + Integer.toHexString(type)); + }; + } + + protected String readStringValue() throws IOException { + int len = readInt(); + byte[] bytes = new byte[len]; + readFully(bytes); + return new String(bytes, StandardCharsets.UTF_8); } } diff --git a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataOutputStream.java b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataOutputStream.java index 2756885007f4..4356ce305e5b 100644 --- a/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataOutputStream.java +++ b/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/TypedDataOutputStream.java @@ -91,16 +91,17 @@ public void writeTypedValue(Object value) throws IOException { this.writeByte('D'); this.writeDouble((Double) value); } else if (valueClz == String.class) { - writeStringValue((String) value); + this.writeByte('U'); + this.writeStringValue((String) value); } else if (valueClz.isEnum()) { - writeStringValue(((Enum) value).name()); + this.writeByte('U'); + this.writeStringValue(((Enum) value).name()); } else { throw new IllegalArgumentException(String.format("Unsupported type: Value: %s, Value type: %s", value, valueClz)); } } - private void writeStringValue(String value) throws IOException { - this.writeByte('U'); + protected void writeStringValue(String value) throws IOException { byte[] bytes = value.getBytes(StandardCharsets.UTF_8); this.writeInt(bytes.length); this.write(bytes); diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java index c4d70dc9e08f..8bd065e7ed11 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerLoader.java @@ -132,9 +132,6 @@ import com.oracle.graal.pointsto.meta.BaseLayerField; import com.oracle.graal.pointsto.meta.BaseLayerMethod; import com.oracle.graal.pointsto.meta.BaseLayerType; -import com.oracle.graal.pointsto.meta.PointsToAnalysisField; -import com.oracle.graal.pointsto.meta.PointsToAnalysisMethod; -import com.oracle.graal.pointsto.meta.PointsToAnalysisType; import com.oracle.graal.pointsto.util.AnalysisError; import com.oracle.graal.pointsto.util.AnalysisFuture; import com.oracle.svm.util.ReflectionUtil; @@ -814,17 +811,17 @@ public boolean hasAnalysisParsedGraph(AnalysisMethod analysisMethod) { public AnalysisParsedGraph getAnalysisParsedGraph(AnalysisMethod analysisMethod) { EconomicMap methodData = getMethodData(analysisMethod); - String encodedAnalyzedGraph = readEncodedGraph(methodData, ANALYSIS_PARSED_GRAPH_TAG); + byte[] encodedAnalyzedGraph = readEncodedGraph(methodData, ANALYSIS_PARSED_GRAPH_TAG); Boolean intrinsic = get(methodData, INTRINSIC_TAG); EncodedGraph analyzedGraph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphDecoder(this, analysisMethod, universe.getSnippetReflection()), encodedAnalyzedGraph); if (hasStrengthenedGraph(analysisMethod)) { - loadAllAnalysisElements(readEncodedGraph(methodData, STRENGTHENED_GRAPH_TAG)); + throw AnalysisError.shouldNotReachHere("Strengthened graphs are not supported until late loading is implemented."); } afterGraphDecodeHook(analyzedGraph); return new AnalysisParsedGraph(analyzedGraph, intrinsic); } - private String readEncodedGraph(EconomicMap methodData, String elementIdentifier) { + private byte[] readEncodedGraph(EconomicMap methodData, String elementIdentifier) { String location = get(methodData, elementIdentifier); int closingBracketAt = location.length() - 1; AnalysisError.guarantee(location.charAt(0) == '@' && location.charAt(closingBracketAt) == ']', "Location must start with '@' and end with ']': %s", location); @@ -844,7 +841,7 @@ private String readEncodedGraph(EconomicMap methodData, String e } catch (IOException e) { throw AnalysisError.shouldNotReachHere("Failed reading a graph from location: " + location, e); } - return new String(bb.array(), ImageLayerWriter.GRAPHS_CHARSET); + return bb.array(); } public boolean hasStrengthenedGraph(AnalysisMethod analysisMethod) { @@ -854,7 +851,7 @@ public boolean hasStrengthenedGraph(AnalysisMethod analysisMethod) { public void setStrengthenedGraph(AnalysisMethod analysisMethod) { EconomicMap methodData = getMethodData(analysisMethod); - String encodedAnalyzedGraph = readEncodedGraph(methodData, STRENGTHENED_GRAPH_TAG); + byte[] encodedAnalyzedGraph = readEncodedGraph(methodData, STRENGTHENED_GRAPH_TAG); EncodedGraph analyzedGraph = (EncodedGraph) ObjectCopier.decode(imageLayerSnapshotUtil.getGraphDecoder(this, analysisMethod, universe.getSnippetReflection()), encodedAnalyzedGraph); afterGraphDecodeHook(analyzedGraph); analysisMethod.setAnalyzedGraph(analyzedGraph); @@ -865,22 +862,6 @@ protected void afterGraphDecodeHook(EncodedGraph encodedGraph) { } - private void loadAllAnalysisElements(String encoding) { - encoding.lines().forEach(this::loadEncodedGraphLineAnalysisElements); - } - - protected void loadEncodedGraphLineAnalysisElements(String line) { - if (line.contains(PointsToAnalysisType.class.getName())) { - getAnalysisType(getId(line)); - } else if (line.contains(PointsToAnalysisMethod.class.getName())) { - getAnalysisMethod(getId(line)); - } else if (line.contains(PointsToAnalysisField.class.getName())) { - getAnalysisField(getId(line)); - } else if (line.contains(ImageHeapInstance.class.getName()) || line.contains(ImageHeapObjectArray.class.getName()) || line.contains(ImageHeapPrimitiveArray.class.getName())) { - getOrCreateConstant(getId(line)); - } - } - protected static int getId(String line) { return Integer.parseInt(line.split(" = ")[1]); } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java index 68df6f379968..af4b6d892904 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerSnapshotUtil.java @@ -46,11 +46,14 @@ import jdk.graal.compiler.nodes.EncodedGraph; import jdk.graal.compiler.nodes.FieldLocationIdentity; import jdk.graal.compiler.util.ObjectCopier; +import jdk.graal.compiler.util.ObjectCopierInputStream; +import jdk.graal.compiler.util.ObjectCopierOutputStream; public class ImageLayerSnapshotUtil { public static final String FILE_NAME_PREFIX = "layer-snapshot-"; - public static final String GRAPHS_FILE_NAME_PREFIX = "layer-snapshot-graphs-"; public static final String FILE_EXTENSION = ".json"; + public static final String GRAPHS_FILE_NAME_PREFIX = "layer-snapshot-graphs-"; + public static final String GRAPHS_FILE_EXTENSION = ".big"; public static final String CONSTRUCTOR_NAME = ""; @@ -188,7 +191,7 @@ public static String snapshotFileName(String imageName) { } public static String snapshotGraphsFileName(String imageName) { - return GRAPHS_FILE_NAME_PREFIX + imageName + FILE_EXTENSION; + return GRAPHS_FILE_NAME_PREFIX + imageName + GRAPHS_FILE_EXTENSION; } public String getTypeIdentifier(AnalysisType type) { @@ -275,15 +278,16 @@ protected ImageHeapConstantBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayer } @Override - public String encode(ObjectCopier.Encoder encoder, Object obj) { + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { ImageHeapConstant imageHeapConstant = (ImageHeapConstant) obj; imageLayerWriter.elementsToPersist.add(new AnalysisFuture<>(() -> imageLayerWriter.persistConstant(UNDEFINED_CONSTANT_ID, UNDEFINED_FIELD_INDEX, imageHeapConstant))); - return String.valueOf(imageHeapConstant.getConstantData().id); + stream.writePackedUnsignedInt(imageHeapConstant.getConstantData().id); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - return imageLayerLoader.getOrCreateConstant(Integer.parseInt(encoded)); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return imageLayerLoader.getOrCreateConstant(id); } } @@ -298,15 +302,16 @@ protected AnalysisTypeBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoade } @Override - public String encode(ObjectCopier.Encoder encoder, Object obj) { + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { AnalysisType type = (AnalysisType) obj; imageLayerWriter.persistType(type); - return String.valueOf(type.getId()); + stream.writePackedUnsignedInt(type.getId()); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - return imageLayerLoader.getAnalysisType(Integer.parseInt(encoded)); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return imageLayerLoader.getAnalysisType(id); } } @@ -323,7 +328,7 @@ protected AnalysisMethodBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoa } @Override - public String encode(ObjectCopier.Encoder encoder, Object obj) { + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { AnalysisMethod method = (AnalysisMethod) obj; AnalysisType declaringClass = method.getDeclaringClass(); imageLayerWriter.elementsToPersist.add(new AnalysisFuture<>(() -> { @@ -334,12 +339,12 @@ public String encode(ObjectCopier.Encoder encoder, Object obj) { imageLayerWriter.persistType(parameter); } imageLayerWriter.persistType(declaringClass); - return String.valueOf(method.getId()); + stream.writePackedUnsignedInt(method.getId()); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - int id = Integer.parseInt(encoded); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); if (id == analysisMethod.getId()) { return analysisMethod; } @@ -358,14 +363,16 @@ protected AnalysisFieldBuiltIn(ImageLayerWriter imageLayerWriter, ImageLayerLoad } @Override - public String encode(ObjectCopier.Encoder encoder, Object obj) { + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { AnalysisField field = (AnalysisField) obj; - return encodeField(field, imageLayerWriter); + int id = encodeField(field, imageLayerWriter); + stream.writePackedUnsignedInt(id); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - return decodeField(imageLayerLoader, encoded); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return decodeField(imageLayerLoader, id); } } @@ -380,27 +387,29 @@ protected FieldLocationIdentityBuiltIn(ImageLayerWriter imageLayerWriter, ImageL } @Override - public String encode(ObjectCopier.Encoder encoder, Object obj) { + public void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { FieldLocationIdentity fieldLocationIdentity = (FieldLocationIdentity) obj; AnalysisField field = (AnalysisField) fieldLocationIdentity.getField(); - return encodeField(field, imageLayerWriter); + int id = encodeField(field, imageLayerWriter); + stream.writePackedUnsignedInt(id); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - return new FieldLocationIdentity(decodeField(imageLayerLoader, encoded)); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + return new FieldLocationIdentity(decodeField(imageLayerLoader, id)); } } - private static String encodeField(AnalysisField field, ImageLayerWriter imageLayerWriter) { + private static int encodeField(AnalysisField field, ImageLayerWriter imageLayerWriter) { String declaringClassId = String.valueOf(field.getDeclaringClass().getId()); if (!imageLayerWriter.fieldsMap.containsKey(declaringClassId) || !imageLayerWriter.fieldsMap.get(declaringClassId).containsKey(field.getName())) { imageLayerWriter.persistField(field); } - return String.valueOf(field.getId()); + return field.getId(); } - private static AnalysisField decodeField(ImageLayerLoader imageLayerLoader, String encoded) { - return imageLayerLoader.getAnalysisField(Integer.parseInt(encoded)); + private static AnalysisField decodeField(ImageLayerLoader imageLayerLoader, int id) { + return imageLayerLoader.getAnalysisField(id); } } diff --git a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java index 68d939aa2795..30e323228ba8 100644 --- a/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java +++ b/substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/heap/ImageLayerWriter.java @@ -101,7 +101,7 @@ import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; @@ -142,8 +142,6 @@ import jdk.vm.ci.meta.ResolvedJavaField; public class ImageLayerWriter { - static final Charset GRAPHS_CHARSET = Charset.defaultCharset(); - protected ImageLayerSnapshotUtil imageLayerSnapshotUtil; private ImageLayerWriterHelper imageLayerWriterHelper; private ImageHeap imageHeap; @@ -161,6 +159,7 @@ public class ImageLayerWriter { private FileInfo fileInfo; private GraphsOutput graphsOutput; private final boolean useSharedLayerGraphs; + private final boolean useSharedLayerStrengthenedGraphs; protected final Set> elementsToPersist = ConcurrentHashMap.newKeySet(); @@ -184,16 +183,14 @@ private static class GraphsOutput { } } - String add(String encodedGraph) { - ByteBuffer encoded = GRAPHS_CHARSET.encode(encodedGraph); - int size = encoded.limit(); - long offset = currentOffset.getAndAdd(size); + String add(byte[] encodedGraph) { + long offset = currentOffset.getAndAdd(encodedGraph.length); try { - tempChannel.write(encoded, offset); + tempChannel.write(ByteBuffer.wrap(encodedGraph), offset); } catch (Exception e) { throw GraalError.shouldNotReachHere(e, "Error during graphs file dumping."); } - return new StringBuilder("@").append(offset).append("[").append(size).append("]").toString(); + return new StringBuilder("@").append(offset).append("[").append(encodedGraph.length).append("]").toString(); } void finish() { @@ -213,6 +210,7 @@ public ImageLayerWriter() { @SuppressWarnings({"this-escape", "unused"}) public ImageLayerWriter(boolean useSharedLayerGraphs) { this.useSharedLayerGraphs = useSharedLayerGraphs; + this.useSharedLayerStrengthenedGraphs = false; this.jsonMap = EconomicMap.create(); this.constantsToRelink = new ArrayList<>(); this.persistedTypeIds = ConcurrentHashMap.newKeySet(); @@ -469,6 +467,10 @@ public void persistAnalysisParsedGraph(AnalysisMethod method) { } public void persistMethodStrengthenedGraph(AnalysisMethod method) { + if (!useSharedLayerStrengthenedGraphs) { + return; + } + EconomicMap methodMap = getMethodMap(method); if (!methodMap.containsKey(STRENGTHENED_GRAPH_TAG)) { @@ -481,8 +483,8 @@ private boolean persistGraph(AnalysisMethod method, EncodedGraph analyzedGraph, if (!useSharedLayerGraphs) { return false; } - String encodedGraph = ObjectCopier.encode(imageLayerSnapshotUtil.getGraphEncoder(this), analyzedGraph); - if (encodedGraph.contains(LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING)) { + byte[] encodedGraph = ObjectCopier.encode(imageLayerSnapshotUtil.getGraphEncoder(this), analyzedGraph); + if (contains(encodedGraph, LambdaUtils.LAMBDA_CLASS_NAME_SUBSTRING.getBytes(StandardCharsets.UTF_8))) { throw AnalysisError.shouldNotReachHere("The graph for the method %s contains a reference to a lambda type, which cannot be decoded: %s".formatted(method, encodedGraph)); } String location = graphsOutput.add(encodedGraph); @@ -490,6 +492,18 @@ private boolean persistGraph(AnalysisMethod method, EncodedGraph analyzedGraph, return true; } + private static boolean contains(byte[] data, byte[] seq) { + outer: for (int i = 0; i <= data.length - seq.length; i++) { + for (int j = 0; j < seq.length; j++) { + if (data[i + j] != seq[j]) { + continue outer; + } + } + return true; + } + return false; + } + private EconomicMap getMethodMap(AnalysisMethod method) { String name = imageLayerSnapshotUtil.getMethodIdentifier(method); EconomicMap methodMap = methodsMap.get(name); diff --git a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java index 8b66364b8b12..d616773b8544 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot.libgraal/src/com/oracle/svm/graal/hotspot/libgraal/LibGraalFeature.java @@ -43,16 +43,6 @@ import java.util.function.Function; import java.util.regex.Pattern; -import com.oracle.graal.pointsto.ObjectScanner; -import com.oracle.graal.pointsto.meta.ObjectReachableCallback; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.graal.hotspot.GetCompilerConfig; -import com.oracle.svm.graal.hotspot.GetJNIConfig; -import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; -import jdk.graal.compiler.hotspot.libgraal.BuildTime; -import jdk.graal.compiler.options.OptionDescriptor; -import jdk.graal.compiler.options.OptionKey; -import jdk.graal.compiler.serviceprovider.LibGraalService; import org.graalvm.collections.EconomicMap; import org.graalvm.jniutils.NativeBridgeSupport; import org.graalvm.nativeimage.ImageSingletons; @@ -63,10 +53,15 @@ import org.graalvm.nativeimage.hosted.RuntimeReflection; import com.oracle.graal.pointsto.BigBang; +import com.oracle.graal.pointsto.ObjectScanner; import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.graal.pointsto.meta.ObjectReachableCallback; import com.oracle.graal.pointsto.reports.CallTreePrinter; import com.oracle.svm.core.SubstrateTargetDescription; import com.oracle.svm.core.option.HostedOptionKey; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.graal.hotspot.GetCompilerConfig; +import com.oracle.svm.graal.hotspot.GetJNIConfig; import com.oracle.svm.hosted.FeatureImpl.AfterAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; @@ -75,8 +70,13 @@ import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.hotspot.CompilerConfigurationFactory; +import jdk.graal.compiler.hotspot.libgraal.BuildTime; import jdk.graal.compiler.nodes.graphbuilderconf.GeneratedInvocationPlugin; import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionDescriptor; +import jdk.graal.compiler.options.OptionKey; +import jdk.graal.compiler.serviceprovider.LibGraalService; import jdk.vm.ci.code.TargetDescription; /** @@ -390,7 +390,7 @@ public void beforeAnalysis(BeforeAnalysisAccess baa) { Consumer.class, // registerAsInHeap Consumer.class, // hostedGraalSetFoldNodePluginClasses String.class, // nativeImageLocationQualifier - String.class // encodedGuestObjects + byte[].class // encodedGuestObjects )); GetCompilerConfig.Result configResult = GetCompilerConfig.from(Options.LibGraalJavaHome.getValue(), bb.getOptions()); for (var e : configResult.opens().entrySet()) { diff --git a/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetCompilerConfig.java b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetCompilerConfig.java index 6ba8b24ac4c4..905eee0745ed 100644 --- a/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetCompilerConfig.java +++ b/substratevm/src/com.oracle.svm.graal.hotspot/src/com/oracle/svm/graal/hotspot/GetCompilerConfig.java @@ -35,7 +35,6 @@ import java.util.Set; import java.util.stream.Collectors; -import jdk.graal.compiler.util.ObjectCopier; import org.graalvm.collections.UnmodifiableEconomicMap; import org.graalvm.collections.UnmodifiableMapCursor; @@ -45,9 +44,10 @@ import jdk.graal.compiler.debug.GraalError; import jdk.graal.compiler.hotspot.HotSpotGraalOptionValues; +import jdk.graal.compiler.hotspot.libgraal.CompilerConfig; import jdk.graal.compiler.options.OptionKey; import jdk.graal.compiler.options.OptionValues; -import jdk.graal.compiler.hotspot.libgraal.CompilerConfig; +import jdk.graal.compiler.util.ObjectCopier; /** * Gets the map created in a JVM subprocess by running {@link CompilerConfig}. @@ -64,7 +64,7 @@ public class GetCompilerConfig { * {@link ObjectCopier}. These packages need to be opened when decoding the returned * string back to an object. */ - public record Result(String encodedConfig, Map> opens) { + public record Result(byte[] encodedConfig, Map> opens) { } /** @@ -171,7 +171,7 @@ public static Result from(Path javaHome, OptionValues options) { throw new GraalError("Interrupted waiting for command: %s", quotedCommand); } try { - String encodedConfig = Files.readString(encodedConfigPath); + byte[] encodedConfig = Files.readAllBytes(encodedConfigPath); if (DEBUG) { System.out.printf("[%d] Executed: %s%n", p.pid(), quotedCommand); System.out.printf("[%d] Output saved in %s%n", p.pid(), encodedConfigPath); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java index a6903f1beba8..5071af10b285 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerLoader.java @@ -77,12 +77,6 @@ import com.oracle.svm.hosted.SVMHost; import com.oracle.svm.hosted.c.CGlobalDataFeature; import com.oracle.svm.hosted.imagelayer.HostedDynamicLayerInfo; -import com.oracle.svm.hosted.meta.HostedArrayClass; -import com.oracle.svm.hosted.meta.HostedField; -import com.oracle.svm.hosted.meta.HostedInstanceClass; -import com.oracle.svm.hosted.meta.HostedInterface; -import com.oracle.svm.hosted.meta.HostedMethod; -import com.oracle.svm.hosted.meta.HostedPrimitiveType; import com.oracle.svm.hosted.meta.HostedUniverse; import com.oracle.svm.hosted.meta.RelocatableConstant; import com.oracle.svm.hosted.util.IdentityHashCodeUtil; @@ -201,20 +195,6 @@ protected void afterGraphDecodeHook(EncodedGraph encodedGraph) { } } - @Override - protected void loadEncodedGraphLineAnalysisElements(String line) { - if (line.contains(HostedInstanceClass.class.getName()) || line.contains(HostedPrimitiveType.class.getName()) || line.contains(HostedArrayClass.class.getName()) || - line.contains(HostedInterface.class.getName())) { - getAnalysisType(getId(line)); - } else if (line.contains(HostedMethod.class.getName())) { - getAnalysisMethod(getId(line)); - } else if (line.contains(HostedField.class.getName())) { - getAnalysisField(getId(line)); - } else { - super.loadEncodedGraphLineAnalysisElements(line); - } - } - @Override protected void prepareConstantRelinking(EconomicMap constantData, int identityHashCode, int id) { Integer tid = get(constantData, CLASS_ID_TAG); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java index 49d4b01b674e..3e8e8466f8ad 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/heap/SVMImageLayerSnapshotUtil.java @@ -29,6 +29,7 @@ import static com.oracle.svm.hosted.reflect.proxy.ProxyRenamingSubstitutionProcessor.isProxyType; import static jdk.graal.compiler.java.LambdaUtils.isLambdaType; +import java.io.IOException; import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.net.URI; @@ -71,6 +72,8 @@ import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; import jdk.graal.compiler.nodes.EncodedGraph; import jdk.graal.compiler.util.ObjectCopier; +import jdk.graal.compiler.util.ObjectCopierInputStream; +import jdk.graal.compiler.util.ObjectCopierOutputStream; import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -269,13 +272,15 @@ protected HostedTypeBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { } @Override - protected String encode(ObjectCopier.Encoder encoder, Object obj) { - return String.valueOf(((HostedType) obj).getWrapped().getId()); + protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + int id = ((HostedType) obj).getWrapped().getId(); + stream.writePackedUnsignedInt(id); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - AnalysisType type = svmImageLayerLoader.getAnalysisType(Integer.parseInt(encoded)); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + AnalysisType type = svmImageLayerLoader.getAnalysisType(id); return svmImageLayerLoader.getHostedUniverse().lookup(type); } } @@ -289,13 +294,14 @@ protected HostedMethodBuiltIn(SVMImageLayerLoader svmImageLayerLoader) { } @Override - protected String encode(ObjectCopier.Encoder encoder, Object obj) { - return String.valueOf(((HostedMethod) obj).getWrapped().getId()); + protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + stream.writePackedUnsignedInt(((HostedMethod) obj).getWrapped().getId()); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - AnalysisMethod method = svmImageLayerLoader.getAnalysisMethod(Integer.parseInt(encoded)); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + int id = stream.readPackedUnsignedInt(); + AnalysisMethod method = svmImageLayerLoader.getAnalysisMethod(id); return svmImageLayerLoader.getHostedUniverse().lookup(method); } } @@ -306,12 +312,11 @@ protected HostedOptionValuesBuiltIn() { } @Override - protected String encode(ObjectCopier.Encoder encoder, Object obj) { - return ""; + protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { return HostedOptionValues.singleton(); } } @@ -325,12 +330,11 @@ protected HostedSnippetReflectionProviderBuiltIn(SnippetReflectionProvider snipp } @Override - protected String encode(ObjectCopier.Encoder encoder, Object obj) { - return ""; + protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { return snippetReflectionProvider; } } @@ -340,14 +344,25 @@ protected CInterfaceLocationIdentityBuiltIn() { super(CInterfaceLocationIdentity.class); } - @Override - protected String encode(ObjectCopier.Encoder encoder, Object obj) { - CInterfaceLocationIdentity cInterfaceLocationIdentity = (CInterfaceLocationIdentity) obj; + private static String asString(Object obj) { + var cInterfaceLocationIdentity = (CInterfaceLocationIdentity) obj; return cInterfaceLocationIdentity.toString(); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { + protected void makeChildIds(ObjectCopier.Encoder encoder, Object obj, ObjectCopier.ObjectPath objectPath) { + encoder.makeStringId(asString(obj), objectPath); + } + + @Override + protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + String string = asString(obj); + encoder.writeString(stream, string); + } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + String encoded = decoder.readString(stream); return new CInterfaceLocationIdentity(encoded); } } @@ -357,16 +372,24 @@ protected FastThreadLocalLocationIdentityBuiltIn() { super(FastThreadLocal.FastThreadLocalLocationIdentity.class); } + private static FastThreadLocal getFastThreadLocal(Object obj) { + var fastThreadLocalLocationIdentity = (FastThreadLocal.FastThreadLocalLocationIdentity) obj; + return ReflectionUtil.readField(FastThreadLocal.FastThreadLocalLocationIdentity.class, "this$0", fastThreadLocalLocationIdentity); + } + + @Override + protected void makeChildIds(ObjectCopier.Encoder encoder, Object obj, ObjectCopier.ObjectPath objectPath) { + makeStaticFieldIds(encoder, objectPath, getFastThreadLocal(obj)); + } + @Override - protected String encode(ObjectCopier.Encoder encoder, Object obj) { - FastThreadLocal.FastThreadLocalLocationIdentity fastThreadLocalLocationIdentity = (FastThreadLocal.FastThreadLocalLocationIdentity) obj; - FastThreadLocal fastThreadLocal = ReflectionUtil.readField(FastThreadLocal.FastThreadLocalLocationIdentity.class, "this$0", fastThreadLocalLocationIdentity); - return encodeStaticField(encoder, fastThreadLocal); + protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + writeStaticField(encoder, stream, getFastThreadLocal(obj)); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - FastThreadLocal fastThreadLocal = getObjectFromStaticField(encoded); + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + FastThreadLocal fastThreadLocal = readStaticFieldAndGetObject(decoder, stream); return fastThreadLocal.getLocationIdentity(); } } @@ -376,30 +399,43 @@ protected VMThreadLocalInfoBuiltIn() { super(VMThreadLocalInfo.class); } - @Override - protected String encode(ObjectCopier.Encoder encoder, Object obj) { - VMThreadLocalInfo vmThreadLocalInfo = (VMThreadLocalInfo) obj; + private static FastThreadLocal getThreadLocal(Object obj) { VMThreadLocalCollector vmThreadLocalCollector = ImageSingletons.lookup(VMThreadLocalCollector.class); - FastThreadLocal fastThreadLocal = vmThreadLocalCollector.getThreadLocal(vmThreadLocalInfo); - return encodeStaticField(encoder, fastThreadLocal); + return vmThreadLocalCollector.getThreadLocal((VMThreadLocalInfo) obj); + } + + @Override + protected void makeChildIds(ObjectCopier.Encoder encoder, Object obj, ObjectCopier.ObjectPath objectPath) { + makeStaticFieldIds(encoder, objectPath, getThreadLocal(obj)); } @Override - protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, String encoding, String encoded) { - FastThreadLocal fastThreadLocal = getObjectFromStaticField(encoded); + protected void encode(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object obj) throws IOException { + writeStaticField(encoder, stream, getThreadLocal(obj)); + } + + @Override + protected Object decode(ObjectCopier.Decoder decoder, Class concreteType, ObjectCopierInputStream stream) throws IOException { + FastThreadLocal fastThreadLocal = readStaticFieldAndGetObject(decoder, stream); return ImageSingletons.lookup(VMThreadLocalCollector.class).forFastThreadLocal(fastThreadLocal); } } - private static String encodeStaticField(ObjectCopier.Encoder encoder, Object object) { + private static void makeStaticFieldIds(ObjectCopier.Encoder encoder, ObjectCopier.ObjectPath objectPath, Object object) { + Field staticField = encoder.getExternalValues().get(object); + encoder.makeStringId(staticField.getDeclaringClass().getName(), objectPath); + encoder.makeStringId(staticField.getName(), objectPath); + } + + private static void writeStaticField(ObjectCopier.Encoder encoder, ObjectCopierOutputStream stream, Object object) throws IOException { Field staticField = encoder.getExternalValues().get(object); - return staticField.getDeclaringClass().getName() + ":" + staticField.getName(); + encoder.writeString(stream, staticField.getDeclaringClass().getName()); + encoder.writeString(stream, staticField.getName()); } - private static T getObjectFromStaticField(String staticField) { - String[] fieldParts = staticField.split(":"); - String className = fieldParts[0]; - String fieldName = fieldParts[1]; + private static T readStaticFieldAndGetObject(ObjectCopier.Decoder decoder, ObjectCopierInputStream stream) throws IOException { + String className = decoder.readString(stream); + String fieldName = decoder.readString(stream); Class declaringClass = ReflectionUtil.lookupClass(false, className); return ReflectionUtil.readStaticField(declaringClass, fieldName); }