From e952b63a2dadd4c903019ecca1deabac63a14a6e Mon Sep 17 00:00:00 2001 From: Shawn Yang Date: Wed, 29 Jan 2025 00:55:29 +0800 Subject: [PATCH] feat(java): jit support for chunk based map serialization (#2027) ## What does this PR do? This PR added jit support for chunk based map serialization, it supports all kinds of map serializaiton by generated code: - final map key and value field type - polymorphic map key and value field type - nested map key and value type This PR also removed the old map serialization protocol code. The new chunk based protocol improve serialized size by **2.3X** at most. data: ``` stringMap: {"k1": "v1", "k2": "v2, ..., "k10": "v10" } intMap: {1:2, 2:4, 3: 6, ..., 10: 20} ``` new protocol: ``` stringMapBytes 68 stringKVStructBytes 69 intMapBytes 28 intKVStructBytes 29 ``` old protocol: ``` stringMapBytes 104 stringKVStructBytes 87 intMapBytes 64 intKVStructBytes 47 ``` And improve performance by 20% ## Related issues Closes #925 #2025 ## Does this PR introduce any user-facing change? - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark [chunk-jmh-result.csv](https://github.com/user-attachments/files/18575900/chunk-jmh-result.csv) [nochunk-jmh-result.csv](https://github.com/user-attachments/files/18575901/nochunk-jmh-result.csv) ![image](https://github.com/user-attachments/assets/754f8e48-b45e-489b-adf5-cca1c5d03f1e) --- java/benchmark/plot_map_benchmark.py | 158 ++++ .../fury/benchmark/MapSerializationSuite.java | 73 +- .../fury/builder/BaseObjectCodecBuilder.java | 763 +++++++++++++++--- .../fury/builder/ObjectCodecBuilder.java | 6 +- .../apache/fury/codegen/CodeGenerator.java | 23 + .../org/apache/fury/codegen/Expression.java | 273 +++++-- .../apache/fury/codegen/ExpressionUtils.java | 108 ++- .../fury/codegen/ExpressionVisitor.java | 17 +- .../apache/fury/collection/Collections.java | 23 + .../org/apache/fury/memory/MemoryBuffer.java | 6 + .../fury/serializer/BufferSerializers.java | 3 +- .../collection/AbstractMapSerializer.java | 569 ++----------- .../fury/serializer/collection/MapFlags.java | 2 +- .../serializer/collection/MapSerializer.java | 11 - .../java/org/apache/fury/type/Generics.java | 17 +- .../java/org/apache/fury/type/TypeUtils.java | 3 + .../fury/codegen/ExpressionVisitorTest.java | 19 +- .../collection/MapSerializersTest.java | 190 ++++- .../fury/format/encoder/ArrayDataForEach.java | 4 +- 19 files changed, 1508 insertions(+), 760 deletions(-) create mode 100644 java/benchmark/plot_map_benchmark.py diff --git a/java/benchmark/plot_map_benchmark.py b/java/benchmark/plot_map_benchmark.py new file mode 100644 index 0000000000..1b53195e58 --- /dev/null +++ b/java/benchmark/plot_map_benchmark.py @@ -0,0 +1,158 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np + +# Load the CSV data +no_chunk_file = "nochunk-jmh-result.csv" +chunk_file = "chunk-jmh-result.csv" +# Read the CSV files +no_chunk_df = pd.read_csv(no_chunk_file) +chunk_df = pd.read_csv(chunk_file) + + +# Function to plot the figures +def plot_benchmark(ax, data1, data2, operation, struct, datatype, title): + # Filter data + filtered_data1 = data1[ + (data1["Benchmark"] == (operation)) + & (data1["struct"] == struct) + & (data1["datatype"] == datatype) + ] + filtered_data2 = data2[ + (data2["Benchmark"] == (operation)) + & (data2["struct"] == struct) + & (data2["datatype"] == datatype) + ] + + # Sort data according to 'mapSize' + filtered_data1 = filtered_data1.sort_values("mapSize") + filtered_data2 = filtered_data2.sort_values("mapSize") + + # Plotting + x_labels = filtered_data1["mapSize"].astype(str).tolist() + x = np.arange(len(x_labels)) + width = 0.35 + + ax.bar( + x - width / 2, + filtered_data1["Score"], + width, + yerr=filtered_data1["ScoreError"], + label="No Chunk", + ) + ax.bar( + x + width / 2, + filtered_data2["Score"], + width, + yerr=filtered_data2["ScoreError"], + label="Chunk", + ) + ax.set_xlabel("Map Size") + ax.set_ylabel("Score (ops/s)") + ax.set_title(title) + ax.set_xticks(x) + ax.set_xticklabels(x_labels) + ax.legend() + + +# Create the subplots for datatype "int" +fig1, axs1 = plt.subplots(2, 2, figsize=(10, 8)) +plot_benchmark( + axs1[0, 0], + no_chunk_df, + chunk_df, + "serialize", + True, + "int", + "Serialize | Datatype: Int", +) +plot_benchmark( + axs1[0, 1], + no_chunk_df, + chunk_df, + "serialize", + True, + "string", + "Serialize | Datatype: String", +) +plot_benchmark( + axs1[1, 0], + no_chunk_df, + chunk_df, + "deserialize", + True, + "int", + "Deserialize | Datatype: Int", +) +plot_benchmark( + axs1[1, 1], + no_chunk_df, + chunk_df, + "deserialize", + True, + "string", + "Deserialize | Datatype: String", +) +plt.tight_layout() +plt.suptitle("Benchmarks for codegen", y=1.05) + + +# Create the subplots for datatype "string" +fig2, axs2 = plt.subplots(2, 2, figsize=(10, 8)) +plot_benchmark( + axs2[0, 0], + no_chunk_df, + chunk_df, + "serialize", + False, + "int", + "Serialize | Datatype: Int", +) +plot_benchmark( + axs2[0, 1], + no_chunk_df, + chunk_df, + "serialize", + False, + "string", + "Serialize | Datatype: String", +) +plot_benchmark( + axs2[1, 0], + no_chunk_df, + chunk_df, + "deserialize", + False, + "int", + "Deserialize | Datatype: Int", +) +plot_benchmark( + axs2[1, 1], + no_chunk_df, + chunk_df, + "deserialize", + False, + "string", + "Deserialize | Datatype: String", +) +plt.tight_layout() +plt.suptitle("Benchmarks for no codegen", y=1.05) + +plt.show() diff --git a/java/benchmark/src/main/java/org/apache/fury/benchmark/MapSerializationSuite.java b/java/benchmark/src/main/java/org/apache/fury/benchmark/MapSerializationSuite.java index fb9490226a..e860196645 100644 --- a/java/benchmark/src/main/java/org/apache/fury/benchmark/MapSerializationSuite.java +++ b/java/benchmark/src/main/java/org/apache/fury/benchmark/MapSerializationSuite.java @@ -23,8 +23,6 @@ import java.util.HashMap; import java.util.Map; import org.apache.fury.Fury; -import org.apache.fury.serializer.Serializer; -import org.apache.fury.serializer.collection.AbstractMapSerializer; import org.openjdk.jmh.Main; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -49,53 +47,70 @@ public static void main(String[] args) throws IOException { Main.main(args); } + public static class StringKVMapStruct { + Map map; + } + + public static class IntKVMapStruct { + Map map; + } + @State(Scope.Thread) public static class MapState { - @Param({"5", "20", "50", "100", "200"}) + @Param({"50"}) public int mapSize; @Param({"false", "true"}) - public boolean enableChunkEncoding; + public boolean struct; + + @Param({"int", "string"}) + public String datatype; - private Map stringMap; - private Map integerMap; - private byte[] stringMapBytes; - private byte[] integerMapBytes; + private Object object; + private byte[] bytes; private Fury fury; @Setup(Level.Trial) public void setup() { fury = Fury.builder().build(); - Serializer serializer = fury.getSerializer(HashMap.class); - ((AbstractMapSerializer) serializer).setUseChunkSerialize(enableChunkEncoding); - stringMap = new HashMap<>(mapSize); - integerMap = new HashMap<>(mapSize); + fury.register(StringKVMapStruct.class); + fury.register(IntKVMapStruct.class); + Map stringMap = new HashMap<>(mapSize); + Map intMap = new HashMap<>(mapSize); for (int i = 0; i < mapSize; i++) { stringMap.put("k" + i, "v" + i); - integerMap.put(i, i * 2); + intMap.put(i, i * 2); + } + StringKVMapStruct stringKVMapStruct = new StringKVMapStruct(); + stringKVMapStruct.map = stringMap; + IntKVMapStruct intKVMapStruct = new IntKVMapStruct(); + intKVMapStruct.map = intMap; + byte[] stringMapBytes = fury.serialize(stringMap); + byte[] intMapBytes = fury.serialize(intMap); + byte[] stringKVStructBytes = fury.serialize(stringKVMapStruct); + byte[] intKVStructBytes = fury.serialize(intKVMapStruct); + switch (datatype) { + case "int": + object = struct ? intKVMapStruct : intMap; + bytes = struct ? intKVStructBytes : intMapBytes; + break; + case "string": + object = struct ? stringKVMapStruct : stringMap; + bytes = struct ? stringKVStructBytes : stringMapBytes; + break; + default: + throw new UnsupportedOperationException(); } - stringMapBytes = fury.serialize(stringMap); - integerMapBytes = fury.serialize(integerMap); } } @Benchmark - public Object serializeStringMap(MapState state) { - return state.fury.serialize(state.stringMap); - } - - @Benchmark - public Object serializeIntMap(MapState state) { - return state.fury.serialize(state.integerMap); - } - - @Benchmark - public Object deserializeStringMap(MapState state) { - return state.fury.deserialize(state.stringMapBytes); + public Object serialize(MapState state) { + return state.fury.serialize(state.object); } @Benchmark - public Object deserializeIntMap(MapState state) { - return state.fury.deserialize(state.integerMapBytes); + public Object deserialize(MapState state) { + return state.fury.deserialize(state.bytes); } } diff --git a/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java b/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java index 7e50919b92..279f679840 100644 --- a/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java +++ b/java/fury-core/src/main/java/org/apache/fury/builder/BaseObjectCodecBuilder.java @@ -21,25 +21,46 @@ import static org.apache.fury.codegen.CodeGenerator.getPackage; import static org.apache.fury.codegen.Expression.Invoke.inlineInvoke; +import static org.apache.fury.codegen.Expression.Literal.ofInt; import static org.apache.fury.codegen.Expression.Reference.fieldRef; import static org.apache.fury.codegen.ExpressionOptimizer.invokeGenerated; +import static org.apache.fury.codegen.ExpressionUtils.add; +import static org.apache.fury.codegen.ExpressionUtils.bitand; +import static org.apache.fury.codegen.ExpressionUtils.bitor; +import static org.apache.fury.codegen.ExpressionUtils.cast; import static org.apache.fury.codegen.ExpressionUtils.eq; +import static org.apache.fury.codegen.ExpressionUtils.eqNull; import static org.apache.fury.codegen.ExpressionUtils.gt; import static org.apache.fury.codegen.ExpressionUtils.inline; +import static org.apache.fury.codegen.ExpressionUtils.invoke; +import static org.apache.fury.codegen.ExpressionUtils.invokeInline; +import static org.apache.fury.codegen.ExpressionUtils.list; import static org.apache.fury.codegen.ExpressionUtils.neq; +import static org.apache.fury.codegen.ExpressionUtils.neqNull; import static org.apache.fury.codegen.ExpressionUtils.not; import static org.apache.fury.codegen.ExpressionUtils.nullValue; +import static org.apache.fury.codegen.ExpressionUtils.or; +import static org.apache.fury.codegen.ExpressionUtils.shift; +import static org.apache.fury.codegen.ExpressionUtils.subtract; import static org.apache.fury.codegen.ExpressionUtils.uninline; import static org.apache.fury.collection.Collections.ofHashSet; import static org.apache.fury.serializer.CodegenSerializer.LazyInitBeanSerializer; +import static org.apache.fury.serializer.collection.AbstractMapSerializer.MAX_CHUNK_SIZE; +import static org.apache.fury.serializer.collection.MapFlags.KEY_DECL_TYPE; +import static org.apache.fury.serializer.collection.MapFlags.TRACKING_KEY_REF; +import static org.apache.fury.serializer.collection.MapFlags.TRACKING_VALUE_REF; +import static org.apache.fury.serializer.collection.MapFlags.VALUE_DECL_TYPE; import static org.apache.fury.type.TypeUtils.CLASS_TYPE; import static org.apache.fury.type.TypeUtils.COLLECTION_TYPE; +import static org.apache.fury.type.TypeUtils.ITERATOR_TYPE; import static org.apache.fury.type.TypeUtils.LIST_TYPE; +import static org.apache.fury.type.TypeUtils.MAP_ENTRY_TYPE; import static org.apache.fury.type.TypeUtils.MAP_TYPE; import static org.apache.fury.type.TypeUtils.OBJECT_TYPE; import static org.apache.fury.type.TypeUtils.PRIMITIVE_BOOLEAN_TYPE; import static org.apache.fury.type.TypeUtils.PRIMITIVE_BYTE_TYPE; import static org.apache.fury.type.TypeUtils.PRIMITIVE_INT_TYPE; +import static org.apache.fury.type.TypeUtils.PRIMITIVE_LONG_TYPE; import static org.apache.fury.type.TypeUtils.PRIMITIVE_VOID_TYPE; import static org.apache.fury.type.TypeUtils.SET_TYPE; import static org.apache.fury.type.TypeUtils.getElementType; @@ -66,6 +87,7 @@ import org.apache.fury.codegen.Expression; import org.apache.fury.codegen.Expression.Assign; import org.apache.fury.codegen.Expression.BitAnd; +import org.apache.fury.codegen.Expression.Break; import org.apache.fury.codegen.Expression.Cast; import org.apache.fury.codegen.Expression.ForEach; import org.apache.fury.codegen.Expression.ForLoop; @@ -75,6 +97,7 @@ import org.apache.fury.codegen.Expression.Literal; import org.apache.fury.codegen.Expression.Reference; import org.apache.fury.codegen.Expression.Return; +import org.apache.fury.codegen.Expression.While; import org.apache.fury.codegen.ExpressionUtils; import org.apache.fury.codegen.ExpressionVisitor.ExprHolder; import org.apache.fury.collection.Tuple2; @@ -201,6 +224,10 @@ protected T visitFury(Function function) { return fury.getJITContext().asyncVisitFury(function); } + private boolean needWriteRef(Class cls) { + return visitFury(fury -> fury.getClassResolver().needToWriteRef(cls)); + } + @Override public String genCode() { ctx.setPackage(getPackage(beanClass)); @@ -243,23 +270,31 @@ public String genCode() { return ctx.genCode(); } - protected static class CutPoint { + protected static class InvokeHint { public boolean genNewMethod; public Set cutPoints = new HashSet<>(); - public CutPoint(boolean genNewMethod, Expression... cutPoints) { + public InvokeHint(boolean genNewMethod, Expression... cutPoints) { this.genNewMethod = genNewMethod; Collections.addAll(this.cutPoints, cutPoints); } - public CutPoint add(Expression cutPoint) { - cutPoints.add(cutPoint); + public InvokeHint add(Expression cutPoint) { + if (cutPoint != null) { + cutPoints.add(cutPoint); + } return this; } + public InvokeHint copy() { + InvokeHint invokeHint = new InvokeHint(genNewMethod); + invokeHint.cutPoints = new HashSet<>(cutPoints); + return invokeHint; + } + @Override public String toString() { - return "CutPoint{" + "genNewMethod=" + genNewMethod + ", cutPoints=" + cutPoints + '}'; + return "InvokeHint{" + "genNewMethod=" + genNewMethod + ", cutPoints=" + cutPoints + '}'; } } @@ -319,7 +354,7 @@ protected Expression serializeFor( boolean generateNewMethod) { // access rawType without jit lock to reduce lock competition. Class rawType = getRawType(typeRef); - if (visitFury(fury -> fury.getClassResolver().needToWriteRef(rawType))) { + if (needWriteRef(rawType)) { return new If( not(writeRefOrNull(buffer, inputObject)), serializeForNotNull(inputObject, buffer, typeRef, serializer, generateNewMethod)); @@ -334,7 +369,7 @@ protected Expression serializeFor( buffer, "writeByte", new Literal(Fury.REF_VALUE_FLAG, PRIMITIVE_BYTE_TYPE)), serializeForNotNull(inputObject, buffer, typeRef, serializer, generateNewMethod)); return new If( - ExpressionUtils.eqNull(inputObject), + eqNull(inputObject), new Invoke(buffer, "writeByte", new Literal(Fury.NULL_FLAG, PRIMITIVE_BYTE_TYPE)), action); } @@ -346,7 +381,8 @@ protected Expression writeRefOrNull(Expression buffer, Expression object) { protected Expression serializeForNotNull( Expression inputObject, Expression buffer, TypeRef typeRef) { - return serializeForNotNull(inputObject, buffer, typeRef, null, false); + boolean genNewMethod = useCollectionSerialization(typeRef) || useMapSerialization(typeRef); + return serializeForNotNull(inputObject, buffer, typeRef, null, genNewMethod); } /** @@ -358,6 +394,12 @@ private Expression serializeForNotNull( return serializeForNotNull(inputObject, buffer, typeRef, null, generateNewMethod); } + private Expression serializeForNotNull( + Expression inputObject, Expression buffer, TypeRef typeRef, Expression serializer) { + boolean genNewMethod = useCollectionSerialization(typeRef) || useMapSerialization(typeRef); + return serializeForNotNull(inputObject, buffer, typeRef, serializer, genNewMethod); + } + private Expression serializeForNotNull( Expression inputObject, Expression buffer, @@ -431,6 +473,10 @@ protected boolean useMapSerialization(Class type) { */ protected abstract boolean isMonomorphic(Class clz); + protected boolean isMonomorphic(TypeRef typeRef) { + return isMonomorphic(typeRef.getRawType()); + } + protected Expression serializeForNotNullObject( Expression inputObject, Expression buffer, TypeRef typeRef, Expression serializer) { Class clz = getRawType(typeRef); @@ -464,7 +510,7 @@ protected Expression writeForNotNullNonFinalObject( writeClassAndObject.add(classResolver.writeClassExpr(classResolverRef, buffer, classInfo)); writeClassAndObject.add( new Invoke( - inlineInvoke(classInfo, "getSerializer", SERIALIZER_TYPE), + invokeInline(classInfo, "getSerializer", getSerializerType(clz)), "write", PRIMITIVE_VOID_TYPE, buffer, @@ -473,6 +519,25 @@ protected Expression writeForNotNullNonFinalObject( ctx, ofHashSet(buffer, inputObject), writeClassAndObject, "writeClassAndObject", false); } + protected Expression writeClassInfo( + Expression buffer, Expression clsExpr, Class declaredClass, boolean returnSerializer) { + ListExpression writeClassAction = new ListExpression(); + Tuple2 classInfoRef = addClassInfoField(declaredClass); + Expression classInfo = classInfoRef.f0; + writeClassAction.add( + new If( + neq(inlineInvoke(classInfo, "getCls", CLASS_TYPE), clsExpr), + new Assign( + classInfo, + inlineInvoke(classResolverRef, "getClassInfo", classInfoTypeRef, clsExpr)))); + writeClassAction.add(classResolver.writeClassExpr(classResolverRef, buffer, classInfo)); + if (returnSerializer) { + writeClassAction.add( + invoke(classInfo, "getSerializer", "serializer", getSerializerType(declaredClass))); + } + return writeClassAction; + } + /** * Returns a serializer expression which will be used to call write/read method to avoid virtual * methods calls in most situations. @@ -533,11 +598,11 @@ protected Expression getOrCreateSerializer(Class cls) { if (hasJITResult) { jitCallbackUpdateFields.put(name, getClassExpr(cls)); ctx.addField( - false, ctx.type(Serializer.class), name, new Cast(newSerializerExpr, SERIALIZER_TYPE)); + false, ctx.type(Serializer.class), name, cast(newSerializerExpr, SERIALIZER_TYPE)); serializerRef = new Reference(name, SERIALIZER_TYPE, false); } else { ctx.addField( - true, ctx.type(serializerClass), name, new Cast(newSerializerExpr, serializerTypeRef)); + true, ctx.type(serializerClass), name, cast(newSerializerExpr, serializerTypeRef)); serializerRef = fieldRef(name, serializerTypeRef); } serializerMap.put(cls, serializerRef); @@ -637,10 +702,13 @@ protected Expression readClassInfo(Class cls, Expression buffer, boolean inli } protected TypeRef getSerializerType(TypeRef objType) { - Class rawType = getRawType(objType); - if (classResolver.isCollection(rawType)) { + return getSerializerType(objType.getRawType()); + } + + protected TypeRef getSerializerType(Class objType) { + if (classResolver.isCollection(objType)) { return COLLECTION_SERIALIZER_TYPE; - } else if (classResolver.isMap(rawType)) { + } else if (classResolver.isMap(objType)) { return MAP_SERIALIZER_TYPE; } return SERIALIZER_TYPE; @@ -677,9 +745,8 @@ protected Expression serializeForCollection( classInfo, inlineInvoke(classResolverRef, "getClassInfo", classInfoTypeRef, clsExpr)))); writeClassAction.add(classResolver.writeClassExpr(classResolverRef, buffer, classInfo)); - serializer = new Invoke(classInfo, "getSerializer", "serializer", SERIALIZER_TYPE, false); - serializer = new Cast(serializer, TypeRef.of(AbstractCollectionSerializer.class)); - writeClassAction.add(serializer, new Return(serializer)); + writeClassAction.add( + new Return(invokeInline(classInfo, "getSerializer", getSerializerType(typeRef)))); // Spit this into a separate method to avoid method too big to inline. serializer = invokeGenerated( @@ -691,7 +758,7 @@ protected Expression serializeForCollection( } } else if (!TypeRef.of(AbstractCollectionSerializer.class).isSupertypeOf(serializer.type())) { serializer = - new Cast(serializer, TypeRef.of(AbstractCollectionSerializer.class), "colSerializer"); + cast(serializer, TypeRef.of(AbstractCollectionSerializer.class), "colSerializer"); } // write collection data. ListExpression actions = new ListExpression(); @@ -724,7 +791,7 @@ protected Expression writeCollectionData( walkPath.add(elementType.toString()); ListExpression builder = new ListExpression(); Class elemClass = TypeUtils.getRawType(elementType); - boolean trackingRef = visitFury(fury -> fury.getClassResolver().needToWriteRef(elemClass)); + boolean trackingRef = needWriteRef(elemClass); Tuple2 writeElementsHeader = writeElementsHeader(elemClass, trackingRef, serializer, buffer, collection); Expression flags = writeElementsHeader.f0; @@ -735,18 +802,18 @@ protected Expression writeCollectionData( builder.add( writeContainerElements(elementType, true, null, null, buffer, collection, size)); } else { - Literal hasNullFlag = Literal.ofInt(CollectionFlags.HAS_NULL); + Literal hasNullFlag = ofInt(CollectionFlags.HAS_NULL); Expression hasNull = eq(new BitAnd(flags, hasNullFlag), hasNullFlag, "hasNull"); builder.add( hasNull, writeContainerElements(elementType, false, null, hasNull, buffer, collection, size)); } } else { - Literal flag = Literal.ofInt(CollectionFlags.NOT_SAME_TYPE); + Literal flag = ofInt(CollectionFlags.NOT_SAME_TYPE); Expression sameElementClass = neq(new BitAnd(flags, flag), flag, "sameElementClass"); builder.add(sameElementClass); // if ((flags & Flags.NOT_DECL_ELEMENT_TYPE) == Flags.NOT_DECL_ELEMENT_TYPE) - Literal notDeclTypeFlag = Literal.ofInt(CollectionFlags.NOT_DECL_ELEMENT_TYPE); + Literal notDeclTypeFlag = ofInt(CollectionFlags.NOT_DECL_ELEMENT_TYPE); Expression isDeclType = neq(new BitAnd(flags, notDeclTypeFlag), notDeclTypeFlag); Expression elemSerializer; // make it in scope of `if(sameElementClass)` boolean maybeDecl = visitFury(f -> f.getClassResolver().isSerializable(elemClass)); @@ -755,12 +822,12 @@ protected Expression writeCollectionData( elemSerializer = new If( isDeclType, - new Cast(getOrCreateSerializer(elemClass), serializerType), - new Cast(writeElementsHeader.f1.inline(), serializerType), + cast(getOrCreateSerializer(elemClass), serializerType), + cast(writeElementsHeader.f1.inline(), serializerType), false, serializerType); } else { - elemSerializer = new Cast(writeElementsHeader.f1.inline(), serializerType); + elemSerializer = cast(writeElementsHeader.f1.inline(), serializerType); } elemSerializer = uninline(elemSerializer); Expression action; @@ -779,7 +846,7 @@ protected Expression writeCollectionData( invokeGenerated(ctx, cutPoint, writeBuilder, "sameElementClassWrite", false), writeContainerElements(elementType, true, null, null, buffer, collection, size)); } else { - Literal hasNullFlag = Literal.ofInt(CollectionFlags.HAS_NULL); + Literal hasNullFlag = ofInt(CollectionFlags.HAS_NULL); Expression hasNull = eq(new BitAnd(flags, hasNullFlag), hasNullFlag, "hasNull"); builder.add(hasNull); ListExpression writeBuilder = new ListExpression(elemSerializer); @@ -800,7 +867,7 @@ protected Expression writeCollectionData( builder.add(action); } walkPath.removeLast(); - return new ListExpression(onCollectionWrite, new If(gt(size, Literal.ofInt(0)), builder)); + return new ListExpression(onCollectionWrite, new If(gt(size, ofInt(0)), builder)); } /** @@ -820,8 +887,8 @@ private Tuple2 writeElementsHeader( if (trackingRef) { bitmap = new ListExpression( - new Invoke(buffer, "writeByte", Literal.ofInt(CollectionFlags.TRACKING_REF)), - Literal.ofInt(CollectionFlags.TRACKING_REF)); + new Invoke(buffer, "writeByte", ofInt(CollectionFlags.TRACKING_REF)), + ofInt(CollectionFlags.TRACKING_REF)); } else { bitmap = new Invoke( @@ -982,16 +1049,15 @@ protected Expression serializeForMap( inlineInvoke(classResolverRef, "getClassInfo", classInfoTypeRef, clsExpr)))); // Note: writeClassExpr is thread safe. writeClassAction.add(classResolver.writeClassExpr(classResolverRef, buffer, classInfo)); - serializer = new Invoke(classInfo, "getSerializer", "serializer", SERIALIZER_TYPE, false); - serializer = new Cast(serializer, TypeRef.of(AbstractMapSerializer.class)); - writeClassAction.add(serializer, new Return(serializer)); + writeClassAction.add( + new Return(invokeInline(classInfo, "getSerializer", MAP_SERIALIZER_TYPE))); // Spit this into a separate method to avoid method too big to inline. serializer = invokeGenerated( ctx, ofHashSet(buffer, map), writeClassAction, "writeMapClassInfo", false); } } else if (!AbstractMapSerializer.class.isAssignableFrom(serializer.type().getRawType())) { - serializer = new Cast(serializer, TypeRef.of(AbstractMapSerializer.class), "mapSerializer"); + serializer = cast(serializer, TypeRef.of(AbstractMapSerializer.class), "mapSerializer"); } Expression write = new If( @@ -1009,36 +1075,296 @@ private Expression jitWriteMap( Tuple2, TypeRef> keyValueType = TypeUtils.getMapKeyValueType(typeRef); TypeRef keyType = keyValueType.f0; TypeRef valueType = keyValueType.f1; - Invoke onMapWrite = - new Invoke(serializer, "onMapWrite", TypeUtils.mapOf(keyType, valueType), buffer, map); - map = onMapWrite; - Invoke size = new Invoke(map, "size", PRIMITIVE_INT_TYPE); - Invoke entrySet = new Invoke(map, "entrySet", "entrySet", SET_TYPE); - ExprHolder exprHolder = ExprHolder.of("buffer", buffer); - ForEach writeKeyValues = - new ForEach( - entrySet, - (i, entryObj) -> { - Expression entry = new Cast(entryObj, TypeRef.of(Map.Entry.class), "entry"); - Expression key = new Invoke(entry, "getKey", "keyObj", OBJECT_TYPE); - key = tryCastIfPublic(key, keyType, "key"); - Expression value = new Invoke(entry, "getValue", "valueObj", OBJECT_TYPE); - value = tryCastIfPublic(value, valueType, "value"); - walkPath.add("key:" + keyType); - boolean genMethodForKey = - useCollectionSerialization(keyType) || useMapSerialization(keyType); - Expression keyAction = - serializeFor(key, exprHolder.get("buffer"), keyType, genMethodForKey); - walkPath.removeLast(); - walkPath.add("value:" + valueType); - boolean genMethodForValue = - useCollectionSerialization(valueType) || useMapSerialization(valueType); - Expression valueAction = - serializeFor(value, exprHolder.get("buffer"), valueType, genMethodForValue); - walkPath.removeLast(); - return new ListExpression(keyAction, valueAction); + map = new Invoke(serializer, "onMapWrite", TypeUtils.mapOf(keyType, valueType), buffer, map); + Expression iterator = + new Invoke(inlineInvoke(map, "entrySet", SET_TYPE), "iterator", ITERATOR_TYPE); + Expression entry = cast(inlineInvoke(iterator, "next", OBJECT_TYPE), MAP_ENTRY_TYPE, "entry"); + boolean keyMonomorphic = isMonomorphic(keyType); + boolean valueMonomorphic = isMonomorphic(valueType); + boolean inline = keyMonomorphic && valueMonomorphic; + Class keyTypeRawType = keyType.getRawType(); + Class valueTypeRawType = valueType.getRawType(); + boolean trackingKeyRef = + visitFury(fury -> fury.getClassResolver().needToWriteRef(keyTypeRawType)); + boolean trackingValueRef = + visitFury(fury -> fury.getClassResolver().needToWriteRef(valueTypeRawType)); + Tuple2 mapKVSerializer = + getMapKVSerializer(keyTypeRawType, valueTypeRawType); + Expression keySerializer = mapKVSerializer.f0; + Expression valueSerializer = mapKVSerializer.f1; + While whileAction = + new While( + neqNull(entry), + () -> { + String method = "writeJavaNullChunk"; + if (keyMonomorphic && valueMonomorphic) { + if (!trackingKeyRef && !trackingValueRef) { + method = "writeNullChunkKVFinalNoRef"; + } + } + Expression writeChunk = writeChunk(buffer, entry, iterator, keyType, valueType); + return new ListExpression( + new Assign( + entry, + inlineInvoke( + serializer, + method, + MAP_ENTRY_TYPE, + buffer, + entry, + iterator, + keySerializer, + valueSerializer)), + new If( + neqNull(entry), inline ? writeChunk : new Assign(entry, inline(writeChunk)))); }); - return new ListExpression(onMapWrite, writeKeyValues); + + return new If(not(inlineInvoke(map, "isEmpty", PRIMITIVE_BOOLEAN_TYPE)), whileAction); + } + + private Tuple2 getMapKVSerializer(Class keyType, Class valueType) { + Expression keySerializer, valueSerializer; + boolean keyMonomorphic = isMonomorphic(keyType); + boolean valueMonomorphic = isMonomorphic(valueType); + if (keyMonomorphic && valueMonomorphic) { + keySerializer = getOrCreateSerializer(keyType); + valueSerializer = getOrCreateSerializer(valueType); + } else if (keyMonomorphic) { + keySerializer = getOrCreateSerializer(keyType); + valueSerializer = nullValue(SERIALIZER_TYPE); + } else if (valueMonomorphic) { + keySerializer = nullValue(SERIALIZER_TYPE); + valueSerializer = getOrCreateSerializer(valueType); + } else { + keySerializer = nullValue(SERIALIZER_TYPE); + valueSerializer = nullValue(SERIALIZER_TYPE); + } + return Tuple2.of(keySerializer, valueSerializer); + } + + protected Expression writeChunk( + Expression buffer, + Expression entry, + Expression iterator, + TypeRef keyType, + TypeRef valueType) { + ListExpression expressions = new ListExpression(); + Expression key = invoke(entry, "getKey", "key", keyType); + Expression value = invoke(entry, "getValue", "value", valueType); + boolean keyMonomorphic = isMonomorphic(keyType); + boolean valueMonomorphic = isMonomorphic(valueType); + Class keyTypeRawType = keyType.getRawType(); + Class valueTypeRawType = valueType.getRawType(); + Expression keyTypeExpr = + keyMonomorphic + ? getClassExpr(keyTypeRawType) + : new Invoke(key, "getClass", "keyType", CLASS_TYPE); + Expression valueTypeExpr = + valueMonomorphic + ? getClassExpr(valueTypeRawType) + : new Invoke(value, "getClass", "valueType", CLASS_TYPE); + Expression writePlaceHolder = new Invoke(buffer, "writeInt16", Literal.ofShort((short) -1)); + Expression chunkSizeOffset = + subtract( + inlineInvoke(buffer, "writerIndex", PRIMITIVE_INT_TYPE), ofInt(1), "chunkSizeOffset"); + expressions.add( + key, + value, + keyTypeExpr, + valueTypeExpr, + writePlaceHolder, + chunkSizeOffset, + writePlaceHolder, + chunkSizeOffset); + + Expression chunkHeader; + Expression keySerializer, valueSerializer; + boolean trackingKeyRef = + visitFury(fury -> fury.getClassResolver().needToWriteRef(keyTypeRawType)); + boolean trackingValueRef = + visitFury(fury -> fury.getClassResolver().needToWriteRef(valueTypeRawType)); + Expression keyWriteRef = Literal.ofBoolean(trackingKeyRef); + Expression valueWriteRef = Literal.ofBoolean(trackingValueRef); + boolean inline = keyMonomorphic && valueMonomorphic; + if (keyMonomorphic && valueMonomorphic) { + keySerializer = getOrCreateSerializer(keyTypeRawType); + valueSerializer = getOrCreateSerializer(valueTypeRawType); + int header = KEY_DECL_TYPE | VALUE_DECL_TYPE; + if (trackingKeyRef) { + header |= TRACKING_KEY_REF; + } + if (trackingValueRef) { + header |= TRACKING_VALUE_REF; + } + chunkHeader = ofInt(header); + expressions.add(chunkHeader); + } else if (keyMonomorphic) { + int header = KEY_DECL_TYPE; + if (trackingKeyRef) { + header |= TRACKING_KEY_REF; + } + keySerializer = getOrCreateSerializer(keyTypeRawType); + walkPath.add("value:" + valueType); + valueSerializer = writeClassInfo(buffer, valueTypeExpr, valueTypeRawType, true); + walkPath.removeLast(); + chunkHeader = ExpressionUtils.ofInt("chunkHeader", header); + expressions.add(chunkHeader); + if (trackingValueRef) { + // value type may be subclass and not track ref. + valueWriteRef = + new Invoke(valueSerializer, "needToWriteRef", "valueWriteRef", PRIMITIVE_BOOLEAN_TYPE); + expressions.add( + new If( + valueWriteRef, + new Assign(chunkHeader, bitor(chunkHeader, ofInt(TRACKING_VALUE_REF))))); + } + } else if (valueMonomorphic) { + walkPath.add("key:" + keyType); + keySerializer = writeClassInfo(buffer, keyTypeExpr, keyTypeRawType, true); + walkPath.removeLast(); + valueSerializer = getOrCreateSerializer(valueTypeRawType); + int header = VALUE_DECL_TYPE; + if (trackingValueRef) { + header |= TRACKING_VALUE_REF; + } + chunkHeader = ExpressionUtils.ofInt("chunkHeader", header); + expressions.add(chunkHeader); + if (trackingKeyRef) { + // key type may be subclass and not track ref. + keyWriteRef = + new Invoke(keySerializer, "needToWriteRef", "keyWriteRef", PRIMITIVE_BOOLEAN_TYPE); + expressions.add( + new If( + keyWriteRef, new Assign(chunkHeader, bitor(chunkHeader, ofInt(TRACKING_KEY_REF))))); + } + } else { + walkPath.add("key:" + keyType); + keySerializer = writeClassInfo(buffer, keyTypeExpr, keyTypeRawType, true); + walkPath.removeLast(); + walkPath.add("value:" + valueType); + valueSerializer = writeClassInfo(buffer, valueTypeExpr, valueTypeRawType, true); + walkPath.removeLast(); + chunkHeader = ExpressionUtils.ofInt("chunkHeader", 0); + expressions.add(chunkHeader); + if (trackingKeyRef) { + // key type may be subclass and not track ref. + keyWriteRef = + new Invoke(keySerializer, "needToWriteRef", "keyWriteRef", PRIMITIVE_BOOLEAN_TYPE); + expressions.add( + new If( + keyWriteRef, new Assign(chunkHeader, bitor(chunkHeader, ofInt(TRACKING_KEY_REF))))); + } + if (trackingValueRef) { + // key type may be subclass and not track ref. + valueWriteRef = + new Invoke(valueSerializer, "needToWriteRef", "valueWriteRef", PRIMITIVE_BOOLEAN_TYPE); + expressions.add( + new If( + valueWriteRef, + new Assign(chunkHeader, bitor(chunkHeader, ofInt(TRACKING_VALUE_REF))))); + } + } + Expression chunkSize = ExpressionUtils.ofInt("chunkSize", 0); + expressions.add( + keySerializer, + valueSerializer, + keyWriteRef, + valueWriteRef, + new Invoke(buffer, "putByte", subtract(chunkSizeOffset, ofInt(1)), chunkHeader), + chunkSize); + Expression keyWriteRefExpr = keyWriteRef; + Expression valueWriteRefExpr = valueWriteRef; + While writeLoop = + new While( + Literal.ofBoolean(true), + () -> { + Expression breakCondition; + if (keyMonomorphic && valueMonomorphic) { + breakCondition = or(eqNull(key), eqNull(value)); + } else if (keyMonomorphic) { + breakCondition = + or( + eqNull(key), + eqNull(value), + neq(inlineInvoke(value, "getClass", CLASS_TYPE), valueTypeExpr)); + } else if (valueMonomorphic) { + breakCondition = + or( + eqNull(key), + eqNull(value), + neq(inlineInvoke(key, "getClass", CLASS_TYPE), keyTypeExpr)); + } else { + breakCondition = + or( + eqNull(key), + eqNull(value), + neq(inlineInvoke(key, "getClass", CLASS_TYPE), keyTypeExpr), + neq(inlineInvoke(value, "getClass", CLASS_TYPE), valueTypeExpr)); + } + Expression writeKey = serializeForNotNull(key, buffer, keyType, keySerializer); + if (trackingKeyRef) { + writeKey = + new If( + or( + not(keyWriteRefExpr), + not( + inlineInvoke( + refResolverRef, + "writeRefOrNull", + PRIMITIVE_BOOLEAN_TYPE, + buffer, + key))), + writeKey); + } + Expression writeValue = + serializeForNotNull(value, buffer, valueType, valueSerializer); + if (trackingValueRef) { + writeValue = + new If( + or( + not(valueWriteRefExpr), + not( + inlineInvoke( + refResolverRef, + "writeRefOrNull", + PRIMITIVE_BOOLEAN_TYPE, + buffer, + value))), + writeValue); + } + return new ListExpression( + new If(breakCondition, new Break()), + writeKey, + writeValue, + new Assign(chunkSize, add(chunkSize, ofInt(1))), + new If(eq(chunkSize, ofInt(MAX_CHUNK_SIZE)), new Break()), + new If( + inlineInvoke(iterator, "hasNext", PRIMITIVE_BOOLEAN_TYPE), + new ListExpression( + new Assign( + entry, + cast(inlineInvoke(iterator, "next", OBJECT_TYPE), MAP_ENTRY_TYPE)), + new Assign( + key, + tryInlineCast(inlineInvoke(entry, "getKey", OBJECT_TYPE), keyType)), + new Assign(value, invokeInline(entry, "getValue", valueType))), + list(new Assign(entry, new Literal(null, MAP_ENTRY_TYPE)), new Break()))); + }); + expressions.add(writeLoop, new Invoke(buffer, "putByte", chunkSizeOffset, chunkSize)); + if (!inline) { + expressions.add(new Return(entry)); + // method too big, spilt it into a new method. + // Generate similar signature as `AbstractMapSerializer.writeJavaChunk`( + // MemoryBuffer buffer, + // Entry entry, + // Iterator> iterator, + // Serializer keySerializer, + // Serializer valueSerializer + // ) + Set params = ofHashSet(buffer, entry, iterator); + return invokeGenerated(ctx, params, expressions, "writeChunk", false); + } + return expressions; } protected Expression readRefOrNull(Expression buffer) { @@ -1065,18 +1391,18 @@ protected Expression deserializeFor( Expression buffer, TypeRef typeRef, Function callback, - CutPoint cutPoint) { + InvokeHint invokeHint) { Class rawType = getRawType(typeRef); if (visitFury(f -> f.getClassResolver().needToWriteRef(rawType))) { - return readRef(buffer, callback, () -> deserializeForNotNull(buffer, typeRef, cutPoint)); + return readRef(buffer, callback, () -> deserializeForNotNull(buffer, typeRef, invokeHint)); } else { if (typeRef.isPrimitive()) { - Expression value = deserializeForNotNull(buffer, typeRef, cutPoint); + Expression value = deserializeForNotNull(buffer, typeRef, invokeHint); // Should put value expr ahead to avoid generated code in wrong scope. return new ListExpression(value, callback.apply(value)); } return readNullable( - buffer, typeRef, callback, () -> deserializeForNotNull(buffer, typeRef, cutPoint)); + buffer, typeRef, callback, () -> deserializeForNotNull(buffer, typeRef, invokeHint)); } } @@ -1116,18 +1442,18 @@ private Expression readNullable( } protected Expression deserializeForNotNull( - Expression buffer, TypeRef typeRef, CutPoint cutPoint) { - return deserializeForNotNull(buffer, typeRef, null, cutPoint); + Expression buffer, TypeRef typeRef, InvokeHint invokeHint) { + return deserializeForNotNull(buffer, typeRef, null, invokeHint); } /** * Return an expression that deserialize an not null inputObject from buffer * . * - * @param cutPoint for generate new method to cut off dependencies. + * @param invokeHint for generate new method to cut off dependencies. */ protected Expression deserializeForNotNull( - Expression buffer, TypeRef typeRef, Expression serializer, CutPoint cutPoint) { + Expression buffer, TypeRef typeRef, Expression serializer, InvokeHint invokeHint) { Class cls = getRawType(typeRef); if (isPrimitive(cls) || isBoxed(cls)) { // for primitive, inline call here to avoid java boxing, rather call corresponding serializer. @@ -1156,12 +1482,14 @@ protected Expression deserializeForNotNull( } Expression obj; if (useCollectionSerialization(typeRef)) { - obj = deserializeForCollection(buffer, typeRef, serializer, cutPoint); + obj = deserializeForCollection(buffer, typeRef, serializer, invokeHint); } else if (useMapSerialization(typeRef)) { - obj = deserializeForMap(buffer, typeRef, serializer, cutPoint); + obj = deserializeForMap(buffer, typeRef, serializer, invokeHint); } else { + if (serializer != null) { + return new Invoke(serializer, "read", OBJECT_TYPE, buffer); + } if (isMonomorphic(cls)) { - Preconditions.checkState(serializer == null); serializer = getOrCreateSerializer(cls); Class returnType = ReflectionUtils.getReturnType(getRawType(serializer.type()), "read"); @@ -1188,7 +1516,7 @@ protected Expression readForNotNullNonFinal( * with {@link BaseObjectCodecBuilder#serializeForCollection} */ protected Expression deserializeForCollection( - Expression buffer, TypeRef typeRef, Expression serializer, CutPoint cutPoint) { + Expression buffer, TypeRef typeRef, Expression serializer, InvokeHint invokeHint) { TypeRef elementType = getElementType(typeRef); if (serializer == null) { Class cls = getRawType(typeRef); @@ -1196,10 +1524,8 @@ protected Expression deserializeForCollection( serializer = getOrCreateSerializer(cls); } else { Expression classInfo = readClassInfo(cls, buffer); - serializer = new Invoke(classInfo, "getSerializer", "serializer", SERIALIZER_TYPE, false); serializer = - new Cast( - serializer, TypeRef.of(AbstractCollectionSerializer.class), "collectionSerializer"); + invoke(classInfo, "getSerializer", "collectionSerializer", COLLECTION_SERIALIZER_TYPE); } } else { checkArgument( @@ -1220,11 +1546,11 @@ protected Expression deserializeForCollection( new ListExpression(collection, hookRead), new Invoke(serializer, "read", OBJECT_TYPE, buffer), false); - if (cutPoint != null && cutPoint.genNewMethod) { - cutPoint.add(buffer); + if (invokeHint != null && invokeHint.genNewMethod) { + invokeHint.add(buffer); return invokeGenerated( ctx, - cutPoint.cutPoints, + invokeHint.cutPoints, new ListExpression(action, new Return(action)), "readCollection", false); @@ -1245,18 +1571,18 @@ protected Expression readCollectionCodegen( if (trackingRef) { builder.add(readContainerElements(elementType, true, null, null, buffer, collection, size)); } else { - Literal hasNullFlag = Literal.ofInt(CollectionFlags.HAS_NULL); + Literal hasNullFlag = ofInt(CollectionFlags.HAS_NULL); Expression hasNull = eq(new BitAnd(flags.inline(), hasNullFlag), hasNullFlag, "hasNull"); builder.add( hasNull, readContainerElements(elementType, false, null, hasNull, buffer, collection, size)); } } else { - Literal notSameTypeFlag = Literal.ofInt(CollectionFlags.NOT_SAME_TYPE); + Literal notSameTypeFlag = ofInt(CollectionFlags.NOT_SAME_TYPE); Expression sameElementClass = neq(new BitAnd(flags, notSameTypeFlag), notSameTypeFlag, "sameElementClass"); // if ((flags & Flags.NOT_DECL_ELEMENT_TYPE) == Flags.NOT_DECL_ELEMENT_TYPE) - Literal notDeclTypeFlag = Literal.ofInt(CollectionFlags.NOT_DECL_ELEMENT_TYPE); + Literal notDeclTypeFlag = ofInt(CollectionFlags.NOT_DECL_ELEMENT_TYPE); Expression isDeclType = neq(new BitAnd(flags, notDeclTypeFlag), notDeclTypeFlag); Invoke serializer = inlineInvoke(readClassInfo(elemClass, buffer), "getSerializer", SERIALIZER_TYPE); @@ -1267,12 +1593,12 @@ protected Expression readCollectionCodegen( elemSerializer = new If( isDeclType, - new Cast(getOrCreateSerializer(elemClass), serializerType), - new Cast(serializer.inline(), serializerType), + cast(getOrCreateSerializer(elemClass), serializerType), + cast(serializer.inline(), serializerType), false, serializerType); } else { - elemSerializer = new Cast(serializer.inline(), serializerType); + elemSerializer = cast(serializer.inline(), serializerType); } elemSerializer = uninline(elemSerializer); builder.add(sameElementClass); @@ -1294,7 +1620,7 @@ protected Expression readCollectionCodegen( false); action = new If(sameElementClass, readBuilder, differentElemTypeRead); } else { - Literal hasNullFlag = Literal.ofInt(CollectionFlags.HAS_NULL); + Literal hasNullFlag = ofInt(CollectionFlags.HAS_NULL); Expression hasNull = eq(new BitAnd(flags, hasNullFlag), hasNullFlag, "hasNull"); builder.add(hasNull); // Same element class read start @@ -1317,8 +1643,7 @@ protected Expression readCollectionCodegen( } walkPath.removeLast(); // place newCollection as last as expr value - return new ListExpression( - size, collection, new If(gt(size, Literal.ofInt(0)), builder), collection); + return new ListExpression(size, collection, new If(gt(size, ofInt(0)), builder), collection); } private Expression readContainerElements( @@ -1364,32 +1689,32 @@ private Expression readContainerElement( Function callback) { boolean genNewMethod = useCollectionSerialization(elementType) || useMapSerialization(elementType); - CutPoint cutPoint = new CutPoint(genNewMethod, buffer); + InvokeHint invokeHint = new InvokeHint(genNewMethod, buffer); Class rawType = getRawType(elementType); boolean finalType = isMonomorphic(rawType); Expression read; if (finalType) { if (trackingRef) { - read = deserializeFor(buffer, elementType, callback, cutPoint); + read = deserializeFor(buffer, elementType, callback, invokeHint); } else { - cutPoint.add(hasNull); + invokeHint.add(hasNull); read = new If( hasNull, - deserializeFor(buffer, elementType, callback, cutPoint), - callback.apply(deserializeForNotNull(buffer, elementType, cutPoint))); + deserializeFor(buffer, elementType, callback, invokeHint.copy()), + callback.apply(deserializeForNotNull(buffer, elementType, invokeHint.copy()))); } } else { - cutPoint.add(elemSerializer); + invokeHint.add(elemSerializer); if (trackingRef) { // eager callback, no need to use ExprHolder. read = readRef( buffer, callback, - () -> deserializeForNotNull(buffer, elementType, elemSerializer, cutPoint)); + () -> deserializeForNotNull(buffer, elementType, elemSerializer, invokeHint)); } else { - cutPoint.add(hasNull); + invokeHint.add(hasNull); read = new If( hasNull, @@ -1397,9 +1722,11 @@ private Expression readContainerElement( buffer, elementType, callback, - () -> deserializeForNotNull(buffer, elementType, elemSerializer, cutPoint)), + () -> + deserializeForNotNull( + buffer, elementType, elemSerializer, invokeHint.copy())), callback.apply( - deserializeForNotNull(buffer, elementType, elemSerializer, cutPoint))); + deserializeForNotNull(buffer, elementType, elemSerializer, invokeHint.copy()))); } } return read; @@ -1410,7 +1737,7 @@ private Expression readContainerElement( * {@link BaseObjectCodecBuilder#serializeForMap} */ protected Expression deserializeForMap( - Expression buffer, TypeRef typeRef, Expression serializer, CutPoint cutPoint) { + Expression buffer, TypeRef typeRef, Expression serializer, InvokeHint invokeHint) { Tuple2, TypeRef> keyValueType = TypeUtils.getMapKeyValueType(typeRef); TypeRef keyType = keyValueType.f0; TypeRef valueType = keyValueType.f1; @@ -1420,8 +1747,7 @@ protected Expression deserializeForMap( serializer = getOrCreateSerializer(cls); } else { Expression classInfo = readClassInfo(cls, buffer); - serializer = new Invoke(classInfo, "getSerializer", SERIALIZER_TYPE); - serializer = new Cast(serializer, TypeRef.of(AbstractMapSerializer.class), "mapSerializer"); + serializer = invoke(classInfo, "getSerializer", "mapSerializer", MAP_SERIALIZER_TYPE); } } else { checkArgument( @@ -1429,49 +1755,230 @@ protected Expression deserializeForMap( "Expected AbstractMapSerializer but got %s", serializer.type()); } + Expression mapSerializer = serializer; Invoke supportHook = inlineInvoke(serializer, "supportCodegenHook", PRIMITIVE_BOOLEAN_TYPE); + ListExpression expressions = new ListExpression(); Expression newMap = new Invoke(serializer, "newMap", MAP_TYPE, buffer); Expression size = new Invoke(serializer, "getAndClearNumElements", "size", PRIMITIVE_INT_TYPE); - Expression start = new Literal(0, PRIMITIVE_INT_TYPE); - Expression step = new Literal(1, PRIMITIVE_INT_TYPE); - ExprHolder exprHolder = ExprHolder.of("map", newMap, "buffer", buffer); + Expression chunkHeader = + new If( + eq(size, ofInt(0)), + ofInt(0), + inlineInvoke(buffer, "readUnsignedByte", PRIMITIVE_INT_TYPE)); + expressions.add(newMap, size, chunkHeader); + Class keyCls = keyType.getRawType(); + Class valueCls = valueType.getRawType(); + boolean keyMonomorphic = isMonomorphic(keyCls); + boolean valueMonomorphic = isMonomorphic(valueCls); + boolean refKey = needWriteRef(keyCls); + boolean refValue = needWriteRef(valueCls); + boolean inline = keyMonomorphic && valueMonomorphic && (!refKey || !refValue); + Tuple2 mapKVSerializer = getMapKVSerializer(keyCls, valueCls); + Expression keySerializer = mapKVSerializer.f0; + Expression valueSerializer = mapKVSerializer.f1; + While chunksLoop = + new While( + gt(size, ofInt(0)), + () -> { + ListExpression exprs = new ListExpression(); + String method = "readJavaNullChunk"; + if (keyMonomorphic && valueMonomorphic) { + if (!refKey && !refValue) { + method = "readNullChunkKVFinalNoRef"; + } + } + Expression sizeAndHeader = + new Invoke( + mapSerializer, + method, + "sizeAndHeader", + PRIMITIVE_LONG_TYPE, + false, + buffer, + newMap, + chunkHeader, + size, + keySerializer, + valueSerializer); + exprs.add( + new Assign( + chunkHeader, cast(bitand(sizeAndHeader, ofInt(0xff)), PRIMITIVE_INT_TYPE)), + new Assign(size, cast(shift(">>>", sizeAndHeader, 8), PRIMITIVE_INT_TYPE))); + Expression sizeAndHeader2 = + readChunk(buffer, newMap, size, keyType, valueType, chunkHeader); + if (inline) { + exprs.add(sizeAndHeader2); + } else { + exprs.add( + new Assign( + chunkHeader, cast(bitand(sizeAndHeader2, ofInt(0xff)), PRIMITIVE_INT_TYPE)), + new Assign(size, cast(shift(">>>", sizeAndHeader2, 8), PRIMITIVE_INT_TYPE))); + } + return exprs; + }); + expressions.add(chunksLoop, newMap); + // first newMap to create map, last newMap as expr value + Expression map = inlineInvoke(serializer, "onMapRead", OBJECT_TYPE, expressions); + Expression action = + new If(supportHook, map, new Invoke(serializer, "read", OBJECT_TYPE, buffer), false); + if (invokeHint != null && invokeHint.genNewMethod) { + invokeHint.add(buffer); + invokeHint.add(serializer); + return invokeGenerated( + ctx, + invokeHint.cutPoints, + new ListExpression(action, new Return(action)), + "readMap", + false); + } + return action; + } + + private Expression readChunk( + Expression buffer, + Expression map, + Expression size, + TypeRef keyType, + TypeRef valueType, + Expression chunkHeader) { + boolean keyMonomorphic = isMonomorphic(keyType); + boolean valueMonomorphic = isMonomorphic(valueType); + Class keyTypeRawType = keyType.getRawType(); + Class valueTypeRawType = valueType.getRawType(); + boolean trackingKeyRef = needWriteRef(keyTypeRawType); + boolean trackingValueRef = needWriteRef(valueTypeRawType); + boolean inline = keyMonomorphic && valueMonomorphic && (!trackingKeyRef || !trackingValueRef); + ListExpression expressions = new ListExpression(buffer); + Expression trackKeyRef = neq(bitand(chunkHeader, ofInt(TRACKING_KEY_REF)), ofInt(0)); + Expression trackValueRef = neq(bitand(chunkHeader, ofInt(TRACKING_VALUE_REF)), ofInt(0)); + Expression keyIsDeclaredType = neq(bitand(chunkHeader, ofInt(KEY_DECL_TYPE)), ofInt(0)); + Expression valueIsDeclaredType = neq(bitand(chunkHeader, ofInt(VALUE_DECL_TYPE)), ofInt(0)); + Expression chunkSize = new Invoke(buffer, "readUnsignedByte", "chunkSize", PRIMITIVE_INT_TYPE); + expressions.add(chunkSize); + + Expression keySerializer, valueSerializer; + if (!keyMonomorphic && !valueMonomorphic) { + keySerializer = readOrGetSerializerForDeclaredType(buffer, keyTypeRawType, keyIsDeclaredType); + valueSerializer = + readOrGetSerializerForDeclaredType(buffer, valueTypeRawType, valueIsDeclaredType); + } else if (!keyMonomorphic) { + keySerializer = readOrGetSerializerForDeclaredType(buffer, keyTypeRawType, keyIsDeclaredType); + valueSerializer = getOrCreateSerializer(valueTypeRawType); + } else if (!valueMonomorphic) { + keySerializer = getOrCreateSerializer(keyTypeRawType); + valueSerializer = + readOrGetSerializerForDeclaredType(buffer, valueTypeRawType, valueIsDeclaredType); + } else { + keySerializer = getOrCreateSerializer(keyTypeRawType); + valueSerializer = getOrCreateSerializer(valueTypeRawType); + } + Expression keySerializerExpr = uninline(keySerializer); + Expression valueSerializerExpr = uninline(valueSerializer); + expressions.add(keySerializerExpr, valueSerializerExpr); ForLoop readKeyValues = new ForLoop( - start, - size, - step, + ofInt(0), + chunkSize, + ofInt(1), i -> { boolean genKeyMethod = useCollectionSerialization(keyType) || useMapSerialization(keyType); boolean genValueMethod = useCollectionSerialization(valueType) || useMapSerialization(valueType); walkPath.add("key:" + keyType); - Expression keyAction = - deserializeFor( - exprHolder.get("buffer"), keyType, e -> e, new CutPoint(genKeyMethod)); + Expression keyAction, valueAction; + InvokeHint keyHint = new InvokeHint(genKeyMethod); + InvokeHint valueHint = new InvokeHint(genValueMethod); + if (genKeyMethod) { + keyHint.add(keySerializerExpr); + } + if (genValueMethod) { + valueHint.add(valueSerializer); + } + if (trackingKeyRef) { + keyAction = + new If( + trackKeyRef, + readRef( + buffer, + expr -> expr, + () -> + deserializeForNotNull(buffer, keyType, keySerializerExpr, keyHint)), + deserializeForNotNull(buffer, keyType, keySerializerExpr, keyHint), + false); + } else { + keyAction = deserializeForNotNull(buffer, keyType, keySerializerExpr, keyHint); + } walkPath.removeLast(); walkPath.add("value:" + valueType); - Expression valueAction = - deserializeFor( - exprHolder.get("buffer"), valueType, e -> e, new CutPoint(genValueMethod)); + if (trackingValueRef) { + valueAction = + new If( + trackValueRef, + readRef( + buffer, + expr -> expr, + () -> + deserializeForNotNull( + buffer, valueType, valueSerializerExpr, valueHint)), + deserializeForNotNull(buffer, valueType, valueSerializerExpr, valueHint), + false); + } else { + valueAction = + deserializeForNotNull(buffer, valueType, valueSerializerExpr, valueHint); + } walkPath.removeLast(); - return new Invoke(exprHolder.get("map"), "put", keyAction, valueAction); + return list( + new Invoke(map, "put", keyAction, valueAction), + new Assign(size, subtract(size, ofInt(1)))); }); - // first newMap to create map, last newMap as expr value - Expression hookRead = new ListExpression(newMap, size, readKeyValues, newMap); - hookRead = new Invoke(serializer, "onMapRead", OBJECT_TYPE, hookRead); - Expression action = - new If(supportHook, hookRead, new Invoke(serializer, "read", OBJECT_TYPE, buffer), false); - if (cutPoint != null && cutPoint.genNewMethod) { - cutPoint.add(buffer); - return invokeGenerated( - ctx, - cutPoint.cutPoints, - new ListExpression(action, new Return(action)), - "readMap", + expressions.add(readKeyValues); + + if (inline) { + expressions.add( + new If( + gt(size, ofInt(0)), + new Assign( + chunkHeader, inlineInvoke(buffer, "readUnsignedByte", PRIMITIVE_INT_TYPE)))); + return expressions; + } else { + Expression returnSizeAndHeader = + new If( + gt(size, ofInt(0)), + new Return( + (bitor( + shift("<<", size, 8), + inlineInvoke(buffer, "readUnsignedByte", PRIMITIVE_INT_TYPE)))), + new Return(ofInt(0))); + expressions.add(returnSizeAndHeader); + // method too big, spilt it into a new method. + // Generate similar signature as `AbstractMapSerializer.writeJavaChunk`( + // MemoryBuffer buffer, + // long size, + // int chunkHeader, + // Serializer keySerializer, + // Serializer valueSerializer + // ) + Set params = ofHashSet(buffer, size, chunkHeader, map); + return invokeGenerated(ctx, params, expressions, "readChunk", false); + } + } + + private Expression readOrGetSerializerForDeclaredType( + Expression buffer, Class type, Expression isDeclaredType) { + if (isMonomorphic(type)) { + return getOrCreateSerializer(type); + } + TypeRef serializerType = getSerializerType(type); + if (ReflectionUtils.isAbstract(type) || type.isInterface()) { + return invoke(readClassInfo(type, buffer), "getSerializer", "serializer", serializerType); + } else { + return new If( + isDeclaredType, + getOrCreateSerializer(type), + invokeInline(readClassInfo(type, buffer), "getSerializer", serializerType), false); } - return action; } @Override diff --git a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java index ded5065fd7..114077913e 100644 --- a/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java +++ b/java/fury-core/src/main/java/org/apache/fury/builder/ObjectCodecBuilder.java @@ -510,9 +510,13 @@ protected Expression createRecord(SortedMap recordComponent return new NewInstance(beanType, params); } - private class FieldsCollector implements Expression { + private class FieldsCollector extends Expression.AbstractExpression { private final TreeMap recordValuesMap = new TreeMap<>(); + protected FieldsCollector() { + super(new Expression[0]); + } + @Override public TypeRef type() { return beanType; diff --git a/java/fury-core/src/main/java/org/apache/fury/codegen/CodeGenerator.java b/java/fury-core/src/main/java/org/apache/fury/codegen/CodeGenerator.java index f26d90d9a4..f5512eae36 100644 --- a/java/fury-core/src/main/java/org/apache/fury/codegen/CodeGenerator.java +++ b/java/fury-core/src/main/java/org/apache/fury/codegen/CodeGenerator.java @@ -480,8 +480,23 @@ static StringBuilder stripIfHasLastNewline(StringBuilder sb) { return sb; } + private static final WeakHashMap, Boolean> sourcePublicLevelAccessible = + new WeakHashMap<>(); + + public static Class getSourcePublicAccessibleParentClass(Class clz) { + while (!sourcePublicAccessible(clz)) { + clz = clz.getSuperclass(); + } + return clz; + } + /** Returns true if class is public accessible from source. */ public static boolean sourcePublicAccessible(Class clz) { + return sourcePublicLevelAccessible.computeIfAbsent( + clz, CodeGenerator::classSourcePublicAccessible); + } + + private static boolean classSourcePublicAccessible(Class clz) { if (clz.isPrimitive()) { return true; } @@ -491,8 +506,16 @@ public static boolean sourcePublicAccessible(Class clz) { return sourcePkgLevelAccessible(clz); } + private static final WeakHashMap, Boolean> sourcePkgLevelAccessible = + new WeakHashMap<>(); + /** Returns true if class is package level accessible from source. */ public static boolean sourcePkgLevelAccessible(Class clz) { + return sourcePkgLevelAccessible.computeIfAbsent( + clz, CodeGenerator::classSourcePkgLevelAccessible); + } + + private static boolean classSourcePkgLevelAccessible(Class clz) { if (clz.isPrimitive()) { return true; } diff --git a/java/fury-core/src/main/java/org/apache/fury/codegen/Expression.java b/java/fury-core/src/main/java/org/apache/fury/codegen/Expression.java index 30b512b8e4..221f9365d9 100644 --- a/java/fury-core/src/main/java/org/apache/fury/codegen/Expression.java +++ b/java/fury-core/src/main/java/org/apache/fury/codegen/Expression.java @@ -26,6 +26,7 @@ import static org.apache.fury.codegen.CodeGenerator.alignIndent; import static org.apache.fury.codegen.CodeGenerator.appendNewlineIfNeeded; import static org.apache.fury.codegen.CodeGenerator.indent; +import static org.apache.fury.collection.Collections.ofArrayList; import static org.apache.fury.type.TypeUtils.BOOLEAN_TYPE; import static org.apache.fury.type.TypeUtils.CLASS_TYPE; import static org.apache.fury.type.TypeUtils.ITERABLE_TYPE; @@ -37,6 +38,7 @@ import static org.apache.fury.type.TypeUtils.PRIMITIVE_SHORT_TYPE; import static org.apache.fury.type.TypeUtils.PRIMITIVE_VOID_TYPE; import static org.apache.fury.type.TypeUtils.STRING_TYPE; +import static org.apache.fury.type.TypeUtils.VOID_TYPE; import static org.apache.fury.type.TypeUtils.boxedType; import static org.apache.fury.type.TypeUtils.defaultValue; import static org.apache.fury.type.TypeUtils.getArrayType; @@ -52,6 +54,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -79,7 +82,6 @@ * tree into subtrees, we can replace dependent expression fields in subtree root nodes with {@link * Reference}. */ -@SuppressWarnings("UnstableApiUsage") public interface Expression { /** @@ -131,9 +133,37 @@ default boolean nullable() { // ####################### Expressions ####################### // ########################################################### - abstract class Inlineable implements Expression { + abstract class AbstractExpression implements Expression { + protected final transient List inputs; + + protected AbstractExpression(Expression input) { + this.inputs = Collections.singletonList(input); + } + + protected AbstractExpression(Expression[] inputs) { + this.inputs = Arrays.asList(inputs); + } + + protected AbstractExpression(List inputs) { + this.inputs = inputs; + } + } + + abstract class Inlineable extends AbstractExpression { protected boolean inlineCall = false; + protected Inlineable(Expression input) { + super(input); + } + + protected Inlineable(Expression[] inputs) { + super(inputs); + } + + protected Inlineable(List inputs) { + super(inputs); + } + public Inlineable inline() { return inline(true); } @@ -149,6 +179,10 @@ abstract class ValueExpression extends Inlineable { // set to others to get a more context-dependent variable name. public String valuePrefix = "value"; + protected ValueExpression(Expression[] inputs) { + super(inputs); + } + public String isNullPrefix() { return "is" + StringUtils.capitalize(valuePrefix) + "Null"; } @@ -158,7 +192,7 @@ public String isNullPrefix() { * A ListExpression is a list of expressions. Use last expression's type/nullable/value/IsNull to * represent ListExpression's type/nullable/value/IsNull. */ - class ListExpression implements Expression { + class ListExpression extends AbstractExpression { private final List expressions; private Expression last; @@ -167,6 +201,7 @@ public ListExpression(Expression... expressions) { } public ListExpression(List expressions) { + super(expressions); this.expressions = expressions; if (!this.expressions.isEmpty()) { this.last = this.expressions.get(this.expressions.size() - 1); @@ -239,7 +274,57 @@ public String toString() { } } - class Literal implements Expression { + class Variable extends AbstractExpression { + private final String namePrefix; + private Expression from; + + public Variable(String namePrefix, Expression from) { + super(from); + this.namePrefix = namePrefix; + this.from = from; + } + + @Override + public TypeRef type() { + return from.type(); + } + + @Override + public boolean nullable() { + return from.nullable(); + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + StringBuilder codeBuilder = new StringBuilder(); + ExprCode targetExprCode = from.genCode(ctx); + if (StringUtils.isNotBlank(targetExprCode.code())) { + codeBuilder.append(targetExprCode.code()).append('\n'); + } + String name = ctx.newName(namePrefix); + String decl = + StringUtils.format( + "${type} ${name} = ${from};", + "type", + ctx.type(type()), + "name", + name, + "from", + targetExprCode.value()); + codeBuilder.append(decl); + return new ExprCode( + codeBuilder.toString(), + targetExprCode.isNull(), + Code.variable(type().getRawType(), name)); + } + + @Override + public String toString() { + return String.format("%s %s = %s;", type(), namePrefix, from); + } + } + + class Literal extends AbstractExpression { public static final Literal True = new Literal(true, PRIMITIVE_BOOLEAN_TYPE); public static final Literal False = new Literal(false, PRIMITIVE_BOOLEAN_TYPE); @@ -247,15 +332,21 @@ class Literal implements Expression { private final TypeRef type; public Literal(String value) { + super(new Expression[0]); this.value = value; this.type = STRING_TYPE; } public Literal(Object value, TypeRef type) { + super(new Expression[0]); this.value = value; this.type = type; } + public static Literal ofBoolean(boolean v) { + return v ? True : False; + } + public static Literal ofByte(short v) { return new Literal(v, PRIMITIVE_BYTE_TYPE); } @@ -362,7 +453,7 @@ public String toString() { } } - class Null implements Expression { + class Null extends AbstractExpression { private TypeRef type; private final boolean typedNull; @@ -371,6 +462,7 @@ public Null(TypeRef type) { } public Null(TypeRef type, boolean typedNull) { + super(new Expression[0]); this.type = type; this.typedNull = typedNull; } @@ -395,8 +487,8 @@ public String toString() { } /** A Reference is a variable/field that can be accessed in the expression's CodegenContext. */ - class Reference implements Expression { - private final String name; + class Reference extends AbstractExpression { + private String name; private final TypeRef type; private final boolean nullable; private final boolean fieldRef; @@ -414,6 +506,7 @@ public Reference(String name, TypeRef type, boolean nullable) { } public Reference(String name, TypeRef type, boolean nullable, boolean fieldRef) { + super(new Expression[0]); this.name = name; this.type = type; this.nullable = nullable; @@ -424,6 +517,10 @@ public static Reference fieldRef(String name, TypeRef type) { return new Reference(name, type, false, true); } + public void setName(String name) { + this.name = name; + } + @Override public TypeRef type() { return type; @@ -462,7 +559,11 @@ public String toString() { } } - class Empty implements Expression { + class Empty extends AbstractExpression { + + public Empty() { + super(new Expression[0]); + } @Override public TypeRef type() { @@ -475,10 +576,11 @@ public ExprCode doGenCode(CodegenContext ctx) { } } - class Block implements Expression { + class Block extends AbstractExpression { private final String code; public Block(String code) { + super(new Expression[0]); this.code = code; } @@ -493,10 +595,11 @@ public ExprCode doGenCode(CodegenContext ctx) { } } - class ForceEvaluate implements Expression { + class ForceEvaluate extends AbstractExpression { private Expression expression; public ForceEvaluate(Expression expression) { + super(expression); this.expression = expression; } @@ -537,6 +640,7 @@ public FieldValue( TypeRef type, boolean fieldNullable, boolean inline) { + super(targetObject); Preconditions.checkArgument(type != null); this.targetObject = targetObject; this.fieldName = fieldName; @@ -628,12 +732,13 @@ public String toString() { } } - class SetField implements Expression { + class SetField extends AbstractExpression { private Expression targetObject; private final String fieldName; private Expression fieldValue; public SetField(Expression targetObject, String fieldName, Expression fieldValue) { + super(targetObject); this.targetObject = targetObject; this.fieldName = fieldName; this.fieldValue = fieldValue; @@ -679,12 +784,15 @@ public String toString() { * An expression to set up a stub in expression tree, so later tree building can replace it with * other expression. */ - class ReplaceStub implements Expression { + class ReplaceStub extends AbstractExpression { private Expression targetObject; - public ReplaceStub() {} + public ReplaceStub() { + super(new Expression[0]); + } public ReplaceStub(Expression targetObject) { + super(targetObject); this.targetObject = targetObject; } @@ -728,6 +836,7 @@ public Cast( String castedValueNamePrefix, boolean inline, boolean ignoreUpcast) { + super(targetObject); this.targetObject = targetObject; this.type = type; this.castedValueNamePrefix = castedValueNamePrefix; @@ -791,13 +900,14 @@ public ExprCode doGenCode(CodegenContext ctx) { } @Override - public Inlineable inline(boolean inlineCall) { + public Cast inline(boolean inlineCall) { if (!inlineCall) { - if (targetObject instanceof Inlineable) { + AbstractExpression expr = (AbstractExpression) targetObject; + if (expr instanceof Inlineable && expr.inputs.size() > 1) { ((Inlineable) targetObject).inlineCall = false; } } - return super.inline(inlineCall); + return (Cast) super.inline(inlineCall); } @Override @@ -827,6 +937,7 @@ public BaseInvoke( boolean returnNullable, boolean inline, boolean needTryCatch) { + super(arguments); this.functionName = functionName; this.type = type; this.arguments = arguments; @@ -1185,7 +1296,7 @@ public String toString() { } } - class NewInstance implements Expression { + class NewInstance extends AbstractExpression { private TypeRef type; private final Class rawType; private String unknownClassName; @@ -1212,6 +1323,7 @@ public NewInstance(TypeRef type, Expression... arguments) { } private NewInstance(TypeRef type, List arguments, Expression outerPointer) { + super(ofArrayList(outerPointer, arguments)); this.type = type; rawType = getRawType(type); this.outerPointer = outerPointer; @@ -1333,7 +1445,7 @@ public String toString() { } } - class NewArray implements Expression { + class NewArray extends AbstractExpression { private TypeRef type; private Expression[] elements; @@ -1345,6 +1457,7 @@ class NewArray implements Expression { private Expression dims; public NewArray(Class elemType, Expression dim) { + super(dim); this.numDimensions = 1; this.elemType = elemType; this.dim = dim; @@ -1359,6 +1472,7 @@ public NewArray(Class elemType, Expression dim) { * @param dims an int[] represent dims */ public NewArray(Class elemType, int numDimensions, Expression dims) { + super(dims); this.numDimensions = numDimensions; this.elemType = elemType; this.dims = dims; @@ -1371,6 +1485,7 @@ public NewArray(Class elemType, int numDimensions, Expression dims) { } public NewArray(TypeRef type, Expression... elements) { + super(elements); this.type = type; this.elements = elements; this.numDimensions = 1; @@ -1482,12 +1597,13 @@ public ExprCode doGenCode(CodegenContext ctx) { } } - class AssignArrayElem implements Expression { + class AssignArrayElem extends AbstractExpression { private Expression targetArray; private Expression value; private Expression[] indexes; public AssignArrayElem(Expression targetArray, Expression value, Expression... indexes) { + super(ofArrayList(targetArray, value, indexes)); this.targetArray = targetArray; this.value = value; this.indexes = indexes; @@ -1529,7 +1645,7 @@ public ExprCode doGenCode(CodegenContext ctx) { } } - class If implements Expression { + class If extends AbstractExpression { private Expression predicate; private Expression trueExpr; private Expression falseExpr; @@ -1537,6 +1653,7 @@ class If implements Expression { private boolean nullable; public If(Expression predicate, Expression trueExpr) { + super(new Expression[] {predicate, trueExpr}); this.predicate = predicate; this.trueExpr = trueExpr; this.nullable = false; @@ -1549,6 +1666,7 @@ public If( Expression falseExpr, boolean nullable, TypeRef type) { + super(new Expression[] {predicate, trueExpr, falseExpr}); this.predicate = predicate; this.trueExpr = trueExpr; this.falseExpr = falseExpr; @@ -1563,6 +1681,7 @@ public If(Expression predicate, Expression trueExpr, Expression falseExpr, boole /** if predicate eval to null, take predicate as false. */ public If(Expression predicate, Expression trueExpr, Expression falseExpr) { + super(new Expression[] {predicate, trueExpr, falseExpr}); this.predicate = predicate; this.trueExpr = trueExpr; this.falseExpr = falseExpr; @@ -1633,7 +1752,6 @@ public ExprCode doGenCode(CodegenContext ctx) { } if (!PRIMITIVE_VOID_TYPE.equals(type.unwrap())) { ExprCode falseEval = falseExpr.doGenCode(ctx); - Preconditions.checkArgument(trueEval.isNull() != null || falseEval.isNull() != null); Preconditions.checkNotNull(trueEval.value()); Preconditions.checkNotNull(falseEval.value()); Class rawType = getRawType(type); @@ -1643,6 +1761,7 @@ public ExprCode doGenCode(CodegenContext ctx) { codeBuilder.append(String.format("%s %s;\n", ctx.type(type), value)); String ifCode; if (nullable) { + Preconditions.checkArgument(trueEval.isNull() != null || falseEval.isNull() != null); codeBuilder.append(String.format("boolean %s = false;\n", isNull)); String trueEvalIsNull; if (trueEval.isNull() == null) { @@ -1759,10 +1878,11 @@ public String toString() { } } - class IsNull implements Expression { + class IsNull extends AbstractExpression { private Expression expr; public IsNull(Expression expr) { + super(expr); this.expr = expr; } @@ -1789,10 +1909,11 @@ public String toString() { } } - class Not implements Expression { + class Not extends AbstractExpression { private Expression target; public Not(Expression target) { + super(target); this.target = target; Preconditions.checkArgument( target.type() == PRIMITIVE_BOOLEAN_TYPE || target.type() == BOOLEAN_TYPE); @@ -1839,6 +1960,7 @@ public BinaryOperator(boolean inline, String operator, Expression left, Expressi protected BinaryOperator( boolean inline, String operator, Expression left, Expression right, TypeRef t) { + super(new Expression[] {left, right}); this.inlineCall = inline; this.operator = operator; this.left = left; @@ -2002,7 +2124,17 @@ public LogicalAnd(Expression left, Expression right) { } public LogicalAnd(boolean inline, Expression left, Expression right) { - super(inline, "&", left, right); + super(inline, "&&", left, right); + } + } + + class LogicalOr extends LogicalOperator { + public LogicalOr(Expression left, Expression right) { + super(true, "||", left, right); + } + + public LogicalOr(boolean inline, Expression left, Expression right) { + super(inline, "||", left, right); } } @@ -2017,8 +2149,8 @@ public LogicalAnd(boolean inline, Expression left, Expression right) { * return val * */ - class While implements Expression { - private final BinaryOperator predicate; + class While extends AbstractExpression { + private final Expression predicate; private Expression action; private Expression[] cutPoints; @@ -2027,11 +2159,15 @@ class While implements Expression { * * @param predicate predicate must be inline. */ - public While(BinaryOperator predicate, Expression action) { + public While(Expression predicate, Expression action) { this(predicate, action, new Expression[0]); } - public While(BinaryOperator predicate, SerializableSupplier action) { + /** + * Use lambda to create a new context, and by capturing variables, we can make the codegen of + * thoese variable expressions happen before while loop. + */ + public While(Expression predicate, SerializableSupplier action) { this( predicate, action.get(), @@ -2039,11 +2175,11 @@ public While(BinaryOperator predicate, SerializableSupplier action) .toArray(new Expression[0])); } - public While(BinaryOperator predicate, Expression action, Expression[] cutPoints) { + public While(Expression predicate, Expression action, Expression[] cutPoints) { + super(ofArrayList(predicate, action, cutPoints)); this.predicate = predicate; this.action = action; this.cutPoints = cutPoints; - Preconditions.checkArgument(predicate.inlineCall, predicate); } @Override @@ -2084,11 +2220,10 @@ public String toString() { } } - class ForEach implements Expression { + class ForEach extends AbstractExpression { private Expression inputObject; - @ClosureVisitable - private final SerializableBiFunction action; + @ClosureVisitable final SerializableBiFunction action; private final TypeRef elementType; @@ -2098,6 +2233,7 @@ class ForEach implements Expression { */ public ForEach( Expression inputObject, SerializableBiFunction action) { + super(inputObject); this.inputObject = inputObject; this.action = action; TypeRef elementType; @@ -2113,6 +2249,7 @@ public ForEach( Expression inputObject, TypeRef beanType, SerializableBiFunction action) { + super(inputObject); this.inputObject = inputObject; this.action = action; this.elementType = beanType; @@ -2208,7 +2345,7 @@ public String toString() { } } - class ZipForEach implements Expression { + class ZipForEach extends AbstractExpression { private Expression left; private Expression right; @@ -2219,6 +2356,7 @@ public ZipForEach( Expression left, Expression right, SerializableTriFunction action) { + super(new Expression[] {left, right}); this.left = left; this.right = right; this.action = action; @@ -2348,22 +2486,27 @@ public ExprCode doGenCode(CodegenContext ctx) { } } - class ForLoop implements Expression { + class ForLoop extends AbstractExpression { public Expression start; public Expression end; public Expression step; - - @ClosureVisitable public final SerializableFunction action; + private final Class maxType; + private final Reference iref; + public Expression loopAction; public ForLoop( Expression start, Expression end, Expression step, SerializableFunction action) { + super(new Expression[] {start, end, step}); this.start = start; this.end = end; this.step = step; - this.action = action; + this.maxType = maxType(getRawType(start.type()), getRawType(end.type())); + Preconditions.checkArgument(maxType.isPrimitive()); + iref = new Reference(String.valueOf(System.identityHashCode(this)), TypeRef.of(maxType)); + this.loopAction = action.apply(iref); } @Override @@ -2373,12 +2516,9 @@ public TypeRef type() { @Override public ExprCode doGenCode(CodegenContext ctx) { - Class maxType = maxType(getRawType(start.type()), getRawType(end.type())); - Preconditions.checkArgument(maxType.isPrimitive()); StringBuilder codeBuilder = new StringBuilder(); String i = ctx.newName("i"); - Reference iref = new Reference(i, TypeRef.of(maxType)); - Expression loopAction = action.apply(iref); + iref.setName(i); ExprCode startExprCode = start.genCode(ctx); ExprCode endExprCode = end.genCode(ctx); ExprCode stepExprCode = step.genCode(ctx); @@ -2420,12 +2560,13 @@ public ExprCode doGenCode(CodegenContext ctx) { *

Since value is declared to be {@code List}, no need to cast in other expression * that need List */ - class ListFromIterable implements Expression { + class ListFromIterable extends AbstractExpression { private final TypeRef elementType; private Expression inputObject; private final TypeRef type; public ListFromIterable(Expression inputObject) { + super(inputObject); this.inputObject = inputObject; Preconditions.checkArgument( getRawType(inputObject.type()) == Iterable.class, @@ -2482,20 +2623,28 @@ public String toString() { } } - class Return implements Expression { + class Return extends AbstractExpression { private Expression expression; + public Return() { + super(new Expression[0]); + } + public Return(Expression expression) { + super(expression); this.expression = expression; } @Override public TypeRef type() { - return expression.type(); + return expression == null ? VOID_TYPE : expression.type(); } @Override public ExprCode doGenCode(CodegenContext ctx) { + if (expression == null) { + return new ExprCode("return;", null, null); + } StringBuilder codeBuilder = new StringBuilder(); ExprCode targetExprCode = expression.genCode(ctx); if (StringUtils.isNotBlank(targetExprCode.code())) { @@ -2507,17 +2656,39 @@ public ExprCode doGenCode(CodegenContext ctx) { @Override public String toString() { - return String.format("return %s", expression); + return expression != null ? String.format("return %s", expression) : "return;"; } } - class Assign implements Expression { - private Expression from; + class Break extends AbstractExpression { + public Break() { + super(new Expression[0]); + } + + @Override + public TypeRef type() { + return PRIMITIVE_VOID_TYPE; + } + + @Override + public ExprCode doGenCode(CodegenContext ctx) { + return new ExprCode("break;", null, null); + } + + @Override + public String toString() { + return "break;"; + } + } + + class Assign extends AbstractExpression { private Expression to; + private Expression from; - public Assign(Expression from, Expression to) { - this.from = from; + public Assign(Expression to, Expression from) { + super(new Expression[] {to, from}); this.to = to; + this.from = from; } @Override @@ -2539,7 +2710,7 @@ public ExprCode doGenCode(CodegenContext ctx) { }); String assign = StringUtils.format( - "${from} = ${to};", "from", fromExprCode.value(), "to", toExprCode.value()); + "${to} = ${from};", "from", fromExprCode.value(), "to", toExprCode.value()); codeBuilder.append(assign); return new ExprCode(codeBuilder.toString(), null, null); } diff --git a/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionUtils.java b/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionUtils.java index fcbbd1239c..64f488aaa6 100644 --- a/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionUtils.java +++ b/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionUtils.java @@ -19,6 +19,8 @@ package org.apache.fury.codegen; +import static org.apache.fury.codegen.CodeGenerator.getSourcePublicAccessibleParentClass; +import static org.apache.fury.codegen.CodeGenerator.sourcePublicAccessible; import static org.apache.fury.codegen.Expression.Arithmetic; import static org.apache.fury.codegen.Expression.Comparator; import static org.apache.fury.codegen.Expression.IsNull; @@ -33,21 +35,36 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.fury.codegen.Expression.BitAnd; +import org.apache.fury.codegen.Expression.BitOr; +import org.apache.fury.codegen.Expression.BitShift; import org.apache.fury.codegen.Expression.Cast; +import org.apache.fury.codegen.Expression.Invoke; +import org.apache.fury.codegen.Expression.ListExpression; +import org.apache.fury.codegen.Expression.LogicalAnd; +import org.apache.fury.codegen.Expression.LogicalOr; import org.apache.fury.codegen.Expression.Null; +import org.apache.fury.codegen.Expression.Variable; +import org.apache.fury.reflect.ReflectionUtils; import org.apache.fury.reflect.TypeRef; import org.apache.fury.util.Preconditions; import org.apache.fury.util.StringUtils; import org.apache.fury.util.function.Functions; /** Expression utils to create expression and code in a more convenient way. */ -@SuppressWarnings("UnstableApiUsage") public class ExpressionUtils { + public static ListExpression list(Expression... expressions) { + return new ListExpression(expressions); + } public static Expression newObjectArray(Expression... expressions) { return new NewArray(TypeRef.of(Object[].class), expressions); } + public static Expression ofInt(String name, int v) { + return new Variable(name, Literal.ofInt(v)); + } + public static Expression valueOf(TypeRef type, Expression value) { return new StaticInvoke(getRawType(type), "valueOf", type, false, value); } @@ -65,6 +82,41 @@ public static Expression eqNull(Expression target) { return eq(target, new Null(target.type())); } + public static Expression neqNull(Expression target) { + Preconditions.checkArgument(!target.type().isPrimitive()); + return neq(target, new Null(target.type())); + } + + public static LogicalAnd and(Expression left, Expression right, String name) { + return new LogicalAnd(false, left, right); + } + + public static LogicalAnd and(Expression left, Expression right) { + return new LogicalAnd(true, left, right); + } + + public static LogicalOr or(Expression left, Expression right, Expression... expressions) { + LogicalOr logicalOr = new LogicalOr(left, right); + for (Expression expression : expressions) { + logicalOr = new LogicalOr(left, expression); + } + return logicalOr; + } + + public static BitOr bitor(Expression left, Expression right) { + return new BitOr(left, right); + } + + public static BitAnd bitand(Expression left, Expression right, String name) { + BitAnd bitAnd = new BitAnd(left, right); + bitAnd.inline(false); + return bitAnd; + } + + public static BitAnd bitand(Expression left, Expression right) { + return new BitAnd(left, right); + } + public static Not not(Expression target) { return new Not(target); } @@ -135,15 +187,65 @@ public static Arithmetic subtract(Expression left, Expression right) { } public static Arithmetic subtract(Expression left, Expression right, String valuePrefix) { - Arithmetic arithmetic = new Arithmetic(true, "-", left, right); + Arithmetic arithmetic = new Arithmetic(false, "-", left, right); arithmetic.valuePrefix = valuePrefix; return arithmetic; } - public static Cast cast(Expression value, TypeRef typeRef) { + public static BitShift shift(String op, Expression target, int numBits) { + return new BitShift(op, target, numBits); + } + + public static BitShift leftShift(Expression target, int numBits) { + return new BitShift("<<", target, numBits); + } + + public static BitShift arithRightShift(Expression target, int numBits) { + return new BitShift(">>", target, numBits); + } + + public static BitShift logicalRightShift(Expression target, int numBits) { + return new BitShift(">>", target, numBits); + } + + public static Expression cast(Expression value, TypeRef typeRef) { + if ((value.type().equals(typeRef) || value.type().isSubtypeOf(typeRef))) { + return value; + } return new Cast(value, typeRef); } + public static Expression cast(Expression value, TypeRef typeRef, String namePrefix) { + return new Cast(value, typeRef, namePrefix); + } + + public static Expression invokeInline( + Expression targetObject, String functionName, TypeRef type) { + return inline(invoke(targetObject, functionName, null, type)); + } + + public static Expression invoke( + Expression targetObject, String functionName, String returnNamePrefix, TypeRef type) { + Class rawType = type.getRawType(); + if (!sourcePublicAccessible(rawType)) { + rawType = getSourcePublicAccessibleParentClass(rawType); + type = type.getSupertype(rawType); + } + Class returnType = + ReflectionUtils.getReturnType(getRawType(targetObject.type()), functionName); + if (!rawType.isAssignableFrom(returnType)) { + if (!sourcePublicAccessible(returnType)) { + returnType = getSourcePublicAccessibleParentClass(returnType); + } + return new Cast( + new Invoke(targetObject, functionName, TypeRef.of(returnType)).inline(), + type, + returnNamePrefix); + } else { + return new Invoke(targetObject, functionName, returnNamePrefix, type); + } + } + public static Expression inline(Expression expression) { return inline(expression, true); } diff --git a/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionVisitor.java b/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionVisitor.java index 1ec1a5e4aa..b5095302a1 100644 --- a/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionVisitor.java +++ b/java/fury-core/src/main/java/org/apache/fury/codegen/ExpressionVisitor.java @@ -92,6 +92,20 @@ public static ExprHolder of( return new ExprHolder(k1, v1, k2, v2, k3, v3, k4, v4); } + public static ExprHolder of( + String k1, + Expression v1, + String k2, + Expression v2, + String k3, + Expression v3, + String k4, + Expression v4, + String k5, + Expression v5) { + return new ExprHolder(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5); + } + public Expression get(String key) { return expressionsMap.get(key); } @@ -156,7 +170,8 @@ public void traverseChildren(Expression expr, Function func) traverseList(expr, ((ListExpression) expr).expressions(), func); } else { for (Field field : ReflectionUtils.getFields(Objects.requireNonNull(expr).getClass(), true)) { - if (!Modifier.isStatic(field.getModifiers())) { + int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) { try { if (Expression.class.isAssignableFrom(field.getType())) { traverseField(expr, field, func); diff --git a/java/fury-core/src/main/java/org/apache/fury/collection/Collections.java b/java/fury-core/src/main/java/org/apache/fury/collection/Collections.java index 5c3125e647..45009c456e 100644 --- a/java/fury-core/src/main/java/org/apache/fury/collection/Collections.java +++ b/java/fury-core/src/main/java/org/apache/fury/collection/Collections.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -84,6 +85,28 @@ public static ArrayList ofArrayList(T e1, T e2, T e3, T e4, T e5) { return list; } + public static ArrayList ofArrayList(T e1, List items) { + ArrayList list = new ArrayList(1 + items.size()); + list.add(e1); + list.addAll(items); + return list; + } + + public static ArrayList ofArrayList(T e1, T... items) { + ArrayList list = new ArrayList(1 + items.length); + list.add(e1); + java.util.Collections.addAll(list, items); + return list; + } + + public static ArrayList ofArrayList(T e1, T e2, T... items) { + ArrayList list = new ArrayList(2 + items.length); + list.add(e1); + list.add(e2); + java.util.Collections.addAll(list, items); + return list; + } + public static HashSet ofHashSet(E e) { HashSet set = new HashSet<>(1); set.add(e); diff --git a/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java b/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java index 8e8b41ee3f..6379b6edfb 100644 --- a/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java +++ b/java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java @@ -399,6 +399,12 @@ public byte getByte(int index) { return UNSAFE.getByte(heapMemory, pos); } + public void putByte(int index, int b) { + final long pos = address + index; + checkPosition(index, pos, 1); + UNSAFE.putByte(heapMemory, pos, (byte) b); + } + public void putByte(int index, byte b) { final long pos = address + index; checkPosition(index, pos, 1); diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/BufferSerializers.java b/java/fury-core/src/main/java/org/apache/fury/serializer/BufferSerializers.java index 6d35e18a23..de91b4b93a 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/BufferSerializers.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/BufferSerializers.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import org.apache.fury.Fury; +import org.apache.fury.memory.ByteBufferUtil; import org.apache.fury.memory.MemoryBuffer; /** Serializers for buffer related classes. */ @@ -47,7 +48,7 @@ public void write(MemoryBuffer buffer, ByteBuffer value) { public ByteBuffer copy(ByteBuffer value) { ByteBuffer dst = ByteBuffer.allocate(value.remaining()); dst.put(value.duplicate()); - dst.rewind(); + ByteBufferUtil.rewind(dst); return dst; } diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java index e228876697..037beebafe 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/AbstractMapSerializer.java @@ -38,6 +38,7 @@ import java.util.Map; import java.util.Map.Entry; import org.apache.fury.Fury; +import org.apache.fury.annotation.CodegenInvoke; import org.apache.fury.collection.IdentityMap; import org.apache.fury.collection.Tuple2; import org.apache.fury.memory.MemoryBuffer; @@ -55,11 +56,10 @@ /** Serializer for all map-like objects. */ @SuppressWarnings({"unchecked", "rawtypes"}) public abstract class AbstractMapSerializer extends Serializer { - private static final int MAX_CHUNK_SIZE = 255; + public static final int MAX_CHUNK_SIZE = 255; protected MethodHandle constructor; protected final boolean supportCodegenHook; - protected boolean useChunkSerialize; private Serializer keySerializer; private Serializer valueSerializer; protected final ClassInfoHolder keyClassInfoWriteCache; @@ -128,55 +128,6 @@ public void setValueSerializer(Serializer valueSerializer) { @Override public void write(MemoryBuffer buffer, T value) { Map map = onMapWrite(buffer, value); - if (useChunkSerialize) { - chunkWriteElements(fury, buffer, map); - } else { - writeElements(fury, buffer, map); - } - } - - @Override - public void xwrite(MemoryBuffer buffer, T value) { - Map map = onMapWrite(buffer, value); - xwriteElements(fury, buffer, map); - } - - protected final void writeElements(Fury fury, MemoryBuffer buffer, Map map) { - Serializer keySerializer = this.keySerializer; - Serializer valueSerializer = this.valueSerializer; - // clear the elemSerializer to avoid conflict if the nested - // serialization has collection field. - // TODO use generics for compatible serializer. - this.keySerializer = null; - this.valueSerializer = null; - if (keySerializer != null && valueSerializer != null) { - javaWriteWithKVSerializers(fury, buffer, map, keySerializer, valueSerializer); - } else if (keySerializer != null) { - ClassResolver classResolver = fury.getClassResolver(); - RefResolver refResolver = fury.getRefResolver(); - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - fury.writeRef(buffer, entry.getKey(), keySerializer); - Object value = entry.getValue(); - writeJavaRefOptimized( - fury, classResolver, refResolver, buffer, value, valueClassInfoWriteCache); - } - } else if (valueSerializer != null) { - ClassResolver classResolver = fury.getClassResolver(); - RefResolver refResolver = fury.getRefResolver(); - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - Object key = entry.getKey(); - writeJavaRefOptimized( - fury, classResolver, refResolver, buffer, key, keyClassInfoWriteCache); - fury.writeRef(buffer, entry.getValue(), valueSerializer); - } - } else { - genericJavaWrite(fury, buffer, map); - } - } - - protected final void chunkWriteElements(Fury fury, MemoryBuffer buffer, Map map) { Serializer keySerializer = this.keySerializer; Serializer valueSerializer = this.valueSerializer; // clear the elemSerializer to avoid conflict if the nested @@ -212,7 +163,13 @@ protected final void chunkWriteElements(Fury fury, MemoryBuffer buffer, Map> iterator, @@ -278,6 +235,33 @@ private void writeNullKeyChunk(MemoryBuffer buffer, Serializer valueSerializer, } } + @CodegenInvoke + public final Entry writeNullChunkKVFinalNoRef( + MemoryBuffer buffer, + Entry entry, + Iterator> iterator, + Serializer keySerializer, + Serializer valueSerializer) { + while (true) { + Object key = entry.getKey(); + Object value = entry.getValue(); + if (key != null) { + if (value != null) { + return entry; + } + buffer.writeByte(NULL_VALUE_KEY_DECL_TYPE); + keySerializer.write(buffer, key); + } else { + writeNullKeyChunk(buffer, valueSerializer, value); + } + if (iterator.hasNext()) { + entry = iterator.next(); + } else { + return null; + } + } + } + // Make byte code of this method smaller than 325 for better jit inline private Entry writeJavaChunk( ClassResolver classResolver, @@ -437,12 +421,16 @@ private Entry writeJavaChunkGeneric( } generics.pushGenericType(keyGenericType); if (!keyWriteRef || !refResolver.writeRefOrNull(buffer, key)) { + fury.incDepth(1); keySerializer.write(buffer, key); + fury.incDepth(-1); } generics.popGenericType(); generics.pushGenericType(valueGenericType); if (!valueWriteRef || !refResolver.writeRefOrNull(buffer, value)) { + fury.incDepth(1); valueSerializer.write(buffer, value); + fury.incDepth(-1); } generics.popGenericType(); // noinspection Duplicates @@ -462,193 +450,6 @@ private Entry writeJavaChunkGeneric( return entry; } - private void javaWriteWithKVSerializers( - Fury fury, - MemoryBuffer buffer, - Map map, - Serializer keySerializer, - Serializer valueSerializer) { - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - Object key = entry.getKey(); - Object value = entry.getValue(); - fury.writeRef(buffer, key, keySerializer); - fury.writeRef(buffer, value, valueSerializer); - } - } - - private void genericJavaWrite(Fury fury, MemoryBuffer buffer, Map map) { - Generics generics = fury.getGenerics(); - GenericType genericType = generics.nextGenericType(); - if (genericType == null) { - generalJavaWrite(fury, buffer, map); - } else { - - // type parameters count for `Map field` will be 0; - // type parameters count for `SubMap field` which SubMap is - // `SubMap implements Map` will be 1; - if (genericType.getTypeParametersCount() < 2) { - genericType = getKVGenericType(genericType); - } - GenericType keyGenericType = genericType.getTypeParameter0(); - GenericType valueGenericType = genericType.getTypeParameter1(); - if (keyGenericType == objType && valueGenericType == objType) { - generalJavaWrite(fury, buffer, map); - return; - } - // Can't avoid push generics repeatedly in loop by stack depth, because push two - // generic type changed generics stack top, which is depth index, update stack top - // and depth will have some cost too. - // Stack depth to avoid push generics repeatedly in loop. - // Note push two generic type changed generics stack top, which is depth index, - // stack top should be updated when using for serialization k/v. - // int depth = fury.getDepth(); - // // depth + 1 to leave a slot for value generics, otherwise value generics will - // // be overwritten by nested key generics. - // fury.setDepth(depth + 1); - // generics.pushGenericType(keyGenericType); - // fury.setDepth(depth); - // generics.pushGenericType(valueGenericType); - boolean keyGenericTypeFinal = keyGenericType.isMonomorphic(); - boolean valueGenericTypeFinal = valueGenericType.isMonomorphic(); - if (keyGenericTypeFinal && valueGenericTypeFinal) { - javaKVTypesFinalWrite(fury, buffer, map, keyGenericType, valueGenericType, generics); - } else if (keyGenericTypeFinal) { - javaKeyTypeFinalWrite(fury, buffer, map, keyGenericType, valueGenericType, generics); - } else if (valueGenericTypeFinal) { - javaValueTypeFinalWrite(fury, buffer, map, keyGenericType, valueGenericType, generics); - } else { - javaKVTypesNonFinalWrite(fury, buffer, map, keyGenericType, valueGenericType, generics); - } - } - } - - private void javaKVTypesFinalWrite( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics) { - Serializer keySerializer = keyGenericType.getSerializer(fury.getClassResolver()); - Serializer valueSerializer = valueGenericType.getSerializer(fury.getClassResolver()); - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - generics.pushGenericType(keyGenericType); - fury.writeRef(buffer, entry.getKey(), keySerializer); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - fury.writeRef(buffer, entry.getValue(), valueSerializer); - generics.popGenericType(); - } - } - - private void javaKeyTypeFinalWrite( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics) { - ClassResolver classResolver = fury.getClassResolver(); - RefResolver refResolver = fury.getRefResolver(); - boolean trackingValueRef = fury.getClassResolver().needToWriteRef(valueGenericType.getCls()); - Serializer keySerializer = keyGenericType.getSerializer(fury.getClassResolver()); - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - generics.pushGenericType(keyGenericType); - fury.writeRef(buffer, entry.getKey(), keySerializer); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - writeJavaRefOptimized( - fury, - classResolver, - refResolver, - trackingValueRef, - buffer, - entry.getValue(), - valueClassInfoWriteCache); - generics.popGenericType(); - } - } - - private void javaValueTypeFinalWrite( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics) { - ClassResolver classResolver = fury.getClassResolver(); - RefResolver refResolver = fury.getRefResolver(); - boolean trackingKeyRef = fury.getClassResolver().needToWriteRef(keyGenericType.getCls()); - Serializer valueSerializer = valueGenericType.getSerializer(fury.getClassResolver()); - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - generics.pushGenericType(keyGenericType); - writeJavaRefOptimized( - fury, - classResolver, - refResolver, - trackingKeyRef, - buffer, - entry.getKey(), - keyClassInfoWriteCache); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - fury.writeRef(buffer, entry.getValue(), valueSerializer); - generics.popGenericType(); - } - } - - private void javaKVTypesNonFinalWrite( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics) { - ClassResolver classResolver = fury.getClassResolver(); - RefResolver refResolver = fury.getRefResolver(); - boolean trackingKeyRef = fury.getClassResolver().needToWriteRef(keyGenericType.getCls()); - boolean trackingValueRef = fury.getClassResolver().needToWriteRef(valueGenericType.getCls()); - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - generics.pushGenericType(keyGenericType); - writeJavaRefOptimized( - fury, - classResolver, - refResolver, - trackingKeyRef, - buffer, - entry.getKey(), - keyClassInfoWriteCache); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - writeJavaRefOptimized( - fury, - classResolver, - refResolver, - trackingValueRef, - buffer, - entry.getValue(), - valueClassInfoWriteCache); - generics.popGenericType(); - } - } - - private void generalJavaWrite(Fury fury, MemoryBuffer buffer, Map map) { - ClassResolver classResolver = fury.getClassResolver(); - RefResolver refResolver = fury.getRefResolver(); - for (Object object : map.entrySet()) { - Map.Entry entry = (Map.Entry) object; - writeJavaRefOptimized( - fury, classResolver, refResolver, buffer, entry.getKey(), keyClassInfoWriteCache); - writeJavaRefOptimized( - fury, classResolver, refResolver, buffer, entry.getValue(), valueClassInfoWriteCache); - } - } - public static void xwriteElements(Fury fury, MemoryBuffer buffer, Map value) { Generics generics = fury.getGenerics(); GenericType genericType = generics.nextGenericType(); @@ -800,7 +601,15 @@ protected void copyEntry(Map originMap, Object[] elements) { } } - protected final void chunkReadElements(MemoryBuffer buffer, int size, Map map) { + @Override + public T read(MemoryBuffer buffer) { + Map map = newMap(buffer); + int size = getAndClearNumElements(); + readElements(buffer, size, map); + return onMapRead(map); + } + + public void readElements(MemoryBuffer buffer, int size, Map map) { Serializer keySerializer = this.keySerializer; Serializer valueSerializer = this.valueSerializer; // clear the elemSerializer to avoid conflict if the nested @@ -808,16 +617,18 @@ protected final void chunkReadElements(MemoryBuffer buffer, int size, Map map) { // TODO use generics for compatible serializer. this.keySerializer = null; this.valueSerializer = null; - if (size == 0) { - return; + int chunkHeader = 0; + if (size != 0) { + chunkHeader = buffer.readUnsignedByte(); } - - int chunkHeader = buffer.readUnsignedByte(); while (size > 0) { long sizeAndHeader = readJavaNullChunk(buffer, map, chunkHeader, size, keySerializer, valueSerializer); - chunkHeader = (int) (sizeAndHeader & 0b11111111); + chunkHeader = (int) (sizeAndHeader & 0xff); size = (int) (sizeAndHeader >>> 8); + if (size == 0) { + break; + } if (keySerializer != null || valueSerializer != null) { sizeAndHeader = readJavaChunk(fury, buffer, map, size, chunkHeader, keySerializer, valueSerializer); @@ -903,6 +714,35 @@ private void readNullKeyChunk( } } + @CodegenInvoke + public long readNullChunkKVFinalNoRef( + MemoryBuffer buffer, + Map map, + int chunkHeader, + long size, + Serializer keySerializer, + Serializer valueSerializer) { + while (true) { + boolean keyHasNull = (chunkHeader & KEY_HAS_NULL) != 0; + boolean valueHasNull = (chunkHeader & VALUE_HAS_NULL) != 0; + if (!keyHasNull) { + if (!valueHasNull) { + return (size << 8) | chunkHeader; + } else { + Object key = keySerializer.read(buffer); + map.put(key, null); + } + } else { + readNullKeyChunk(buffer, map, chunkHeader, valueSerializer, valueHasNull); + } + if (size-- == 0) { + return 0; + } else { + chunkHeader = buffer.readUnsignedByte(); + } + } + } + private long readJavaChunk( Fury fury, MemoryBuffer buffer, @@ -970,11 +810,15 @@ private long readJavaChunkGeneric( } for (int i = 0; i < chunkSize; i++) { generics.pushGenericType(keyGenericType); + fury.incDepth(1); Object key = trackKeyRef ? fury.readRef(buffer, keySerializer) : keySerializer.read(buffer); + fury.incDepth(-1); generics.popGenericType(); generics.pushGenericType(valueGenericType); + fury.incDepth(1); Object value = trackValueRef ? fury.readRef(buffer, valueSerializer) : valueSerializer.read(buffer); + fury.incDepth(-1); generics.popGenericType(); map.put(key, value); size--; @@ -982,170 +826,6 @@ private long readJavaChunkGeneric( return size > 0 ? (size << 8) | buffer.readUnsignedByte() : 0; } - @SuppressWarnings("unchecked") - protected final void readElements(MemoryBuffer buffer, int size, Map map) { - Serializer keySerializer = this.keySerializer; - Serializer valueSerializer = this.valueSerializer; - // clear the elemSerializer to avoid conflict if the nested - // serialization has collection field. - // TODO use generics for compatible serializer. - this.keySerializer = null; - this.valueSerializer = null; - if (keySerializer != null && valueSerializer != null) { - for (int i = 0; i < size; i++) { - Object key = fury.readRef(buffer, keySerializer); - Object value = fury.readRef(buffer, valueSerializer); - map.put(key, value); - } - } else if (keySerializer != null) { - for (int i = 0; i < size; i++) { - Object key = fury.readRef(buffer, keySerializer); - map.put(key, fury.readRef(buffer, keyClassInfoReadCache)); - } - } else if (valueSerializer != null) { - for (int i = 0; i < size; i++) { - Object key = fury.readRef(buffer); - Object value = fury.readRef(buffer, valueSerializer); - map.put(key, value); - } - } else { - genericJavaRead(fury, buffer, map, size); - } - } - - private void genericJavaRead(Fury fury, MemoryBuffer buffer, Map map, int size) { - Generics generics = fury.getGenerics(); - GenericType genericType = generics.nextGenericType(); - if (genericType == null) { - generalJavaRead(fury, buffer, map, size); - } else { - if (genericType.getTypeParametersCount() < 2) { - genericType = getKVGenericType(genericType); - } - GenericType keyGenericType = genericType.getTypeParameter0(); - GenericType valueGenericType = genericType.getTypeParameter1(); - if (keyGenericType == objType && valueGenericType == objType) { - generalJavaRead(fury, buffer, map, size); - return; - } - boolean keyGenericTypeFinal = keyGenericType.isMonomorphic(); - boolean valueGenericTypeFinal = valueGenericType.isMonomorphic(); - if (keyGenericTypeFinal && valueGenericTypeFinal) { - javaKVTypesFinalRead(fury, buffer, map, keyGenericType, valueGenericType, generics, size); - } else if (keyGenericTypeFinal) { - javaKeyTypeFinalRead(fury, buffer, map, keyGenericType, valueGenericType, generics, size); - } else if (valueGenericTypeFinal) { - javaValueTypeFinalRead(fury, buffer, map, keyGenericType, valueGenericType, generics, size); - } else { - javaKVTypesNonFinalRead( - fury, buffer, map, keyGenericType, valueGenericType, generics, size); - } - generics.popGenericType(); - } - } - - private void javaKVTypesFinalRead( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics, - int size) { - Serializer keySerializer = keyGenericType.getSerializer(fury.getClassResolver()); - Serializer valueSerializer = valueGenericType.getSerializer(fury.getClassResolver()); - for (int i = 0; i < size; i++) { - generics.pushGenericType(keyGenericType); - Object key = fury.readRef(buffer, keySerializer); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - Object value = fury.readRef(buffer, valueSerializer); - generics.popGenericType(); - map.put(key, value); - } - } - - private void javaKeyTypeFinalRead( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics, - int size) { - RefResolver refResolver = fury.getRefResolver(); - boolean trackingValueRef = fury.getClassResolver().needToWriteRef(valueGenericType.getCls()); - Serializer keySerializer = keyGenericType.getSerializer(fury.getClassResolver()); - for (int i = 0; i < size; i++) { - generics.pushGenericType(keyGenericType); - Object key = fury.readRef(buffer, keySerializer); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - Object value = - readJavaRefOptimized( - fury, refResolver, trackingValueRef, buffer, valueClassInfoWriteCache); - generics.popGenericType(); - map.put(key, value); - } - } - - private void javaValueTypeFinalRead( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics, - int size) { - boolean trackingKeyRef = fury.getClassResolver().needToWriteRef(keyGenericType.getCls()); - Serializer valueSerializer = valueGenericType.getSerializer(fury.getClassResolver()); - RefResolver refResolver = fury.getRefResolver(); - for (int i = 0; i < size; i++) { - generics.pushGenericType(keyGenericType); - Object key = - readJavaRefOptimized(fury, refResolver, trackingKeyRef, buffer, keyClassInfoWriteCache); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - Object value = fury.readRef(buffer, valueSerializer); - generics.popGenericType(); - map.put(key, value); - } - } - - private void javaKVTypesNonFinalRead( - Fury fury, - MemoryBuffer buffer, - Map map, - GenericType keyGenericType, - GenericType valueGenericType, - Generics generics, - int size) { - ClassResolver classResolver = fury.getClassResolver(); - RefResolver refResolver = fury.getRefResolver(); - boolean trackingKeyRef = classResolver.needToWriteRef(keyGenericType.getCls()); - boolean trackingValueRef = classResolver.needToWriteRef(valueGenericType.getCls()); - for (int i = 0; i < size; i++) { - generics.pushGenericType(keyGenericType); - Object key = - readJavaRefOptimized(fury, refResolver, trackingKeyRef, buffer, keyClassInfoWriteCache); - generics.popGenericType(); - generics.pushGenericType(valueGenericType); - Object value = - readJavaRefOptimized( - fury, refResolver, trackingValueRef, buffer, valueClassInfoWriteCache); - generics.popGenericType(); - map.put(key, value); - } - } - - private void generalJavaRead(Fury fury, MemoryBuffer buffer, Map map, int size) { - for (int i = 0; i < size; i++) { - Object key = fury.readRef(buffer, keyClassInfoReadCache); - Object value = fury.readRef(buffer, valueClassInfoReadCache); - map.put(key, value); - } - } - @SuppressWarnings("unchecked") public static void xreadElements(Fury fury, MemoryBuffer buffer, Map map, int size) { Generics generics = fury.getGenerics(); @@ -1218,14 +898,6 @@ public final boolean supportCodegenHook() { return supportCodegenHook; } - public boolean isUseChunkSerialize() { - return useChunkSerialize; - } - - public void setUseChunkSerialize(boolean useChunkSerialize) { - this.useChunkSerialize = useChunkSerialize; - } - /** * Write data except size and elements. * @@ -1239,44 +911,6 @@ public void setUseChunkSerialize(boolean useChunkSerialize) { */ public abstract Map onMapWrite(MemoryBuffer buffer, T value); - /** Check null first to avoid ref tracking for some types with ref tracking disabled. */ - private void writeJavaRefOptimized( - Fury fury, - ClassResolver classResolver, - RefResolver refResolver, - MemoryBuffer buffer, - Object obj, - ClassInfoHolder classInfoHolder) { - if (!refResolver.writeNullFlag(buffer, obj)) { - fury.writeRef(buffer, obj, classResolver.getClassInfo(obj.getClass(), classInfoHolder)); - } - } - - private void writeJavaRefOptimized( - Fury fury, - ClassResolver classResolver, - RefResolver refResolver, - boolean trackingRef, - MemoryBuffer buffer, - Object obj, - ClassInfoHolder classInfoHolder) { - if (trackingRef) { - if (!refResolver.writeNullFlag(buffer, obj)) { - fury.writeRef(buffer, obj, classResolver.getClassInfo(obj.getClass(), classInfoHolder)); - } - } else { - if (obj == null) { - buffer.writeByte(Fury.NULL_FLAG); - } else { - buffer.writeByte(Fury.NOT_NULL_VALUE_FLAG); - fury.writeNonRef(buffer, obj, classResolver.getClassInfo(obj.getClass(), classInfoHolder)); - } - } - } - - @Override - public abstract T read(MemoryBuffer buffer); - /** * Read data except size and elements, return empty map to be filled. * @@ -1342,29 +976,4 @@ public void setNumElements(int numElements) { public abstract T onMapCopy(Map map); public abstract T onMapRead(Map map); - - private Object readJavaRefOptimized( - Fury fury, - RefResolver refResolver, - boolean trackingRef, - MemoryBuffer buffer, - ClassInfoHolder classInfoHolder) { - if (trackingRef) { - int nextReadRefId = refResolver.tryPreserveRefId(buffer); - if (nextReadRefId >= Fury.NOT_NULL_VALUE_FLAG) { - Object obj = fury.readNonRef(buffer, classInfoHolder); - refResolver.setReadObject(nextReadRefId, obj); - return obj; - } else { - return refResolver.getReadObject(); - } - } else { - byte headFlag = buffer.readByte(); - if (headFlag == Fury.NULL_FLAG) { - return null; - } else { - return fury.readNonRef(buffer, classInfoHolder); - } - } - } } diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapFlags.java b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapFlags.java index d1e8da4c2c..3f4e19b6e3 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapFlags.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapFlags.java @@ -21,7 +21,7 @@ public class MapFlags { /** Whether track key ref. */ - public static int TRACKING_KEY_REF = 0b0; + public static int TRACKING_KEY_REF = 0b1; /** Whether key has null. */ public static int KEY_HAS_NULL = 0b10; diff --git a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializer.java b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializer.java index ca40de4c0f..4019aed5f9 100644 --- a/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializer.java +++ b/java/fury-core/src/main/java/org/apache/fury/serializer/collection/MapSerializer.java @@ -62,17 +62,6 @@ public T onMapCopy(Map map) { return (T) map; } - @Override - public T read(MemoryBuffer buffer) { - Map map = newMap(buffer); - if (useChunkSerialize) { - chunkReadElements(buffer, getAndClearNumElements(), map); - } else { - readElements(buffer, getAndClearNumElements(), map); - } - return onMapRead(map); - } - @Override public T onMapRead(Map map) { return (T) map; diff --git a/java/fury-core/src/main/java/org/apache/fury/type/Generics.java b/java/fury-core/src/main/java/org/apache/fury/type/Generics.java index ceb7b768f3..e071e12543 100644 --- a/java/fury-core/src/main/java/org/apache/fury/type/Generics.java +++ b/java/fury-core/src/main/java/org/apache/fury/type/Generics.java @@ -56,17 +56,22 @@ public void pushGenericType(GenericType fieldType) { int size = genericTypesSize++; GenericType[] genericTypes = this.genericTypes; if (size == genericTypes.length) { - genericTypes = new GenericType[genericTypes.length << 1]; - System.arraycopy(this.genericTypes, 0, genericTypes, 0, size); - this.genericTypes = genericTypes; - int[] depthsNew = new int[depths.length << 1]; - System.arraycopy(depths, 0, depthsNew, 0, size); - depths = depthsNew; + genericTypes = allocateGenericTypes(genericTypes, size); } genericTypes[size] = fieldType; depths[size] = fury.getDepth(); } + private GenericType[] allocateGenericTypes(GenericType[] genericTypes, int size) { + genericTypes = new GenericType[genericTypes.length << 1]; + System.arraycopy(this.genericTypes, 0, genericTypes, 0, size); + this.genericTypes = genericTypes; + int[] depthsNew = new int[depths.length << 1]; + System.arraycopy(depths, 0, depthsNew, 0, size); + depths = depthsNew; + return genericTypes; + } + /** * Removes the generic types being tracked since the corresponding {@link * #pushGenericType(GenericType)}. This is safe to call even if {@link diff --git a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java index b7462554b5..65c0515a99 100644 --- a/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java +++ b/java/fury-core/src/main/java/org/apache/fury/type/TypeUtils.java @@ -90,12 +90,15 @@ public class TypeUtils { public static final TypeRef INSTANT_TYPE = TypeRef.of(Instant.class); public static final TypeRef BINARY_TYPE = TypeRef.of(byte[].class); public static final TypeRef ITERABLE_TYPE = TypeRef.of(Iterable.class); + + public static final TypeRef ITERATOR_TYPE = TypeRef.of(Iterator.class); public static final TypeRef COLLECTION_TYPE = TypeRef.of(Collection.class); public static final TypeRef LIST_TYPE = TypeRef.of(List.class); public static final TypeRef ARRAYLIST_TYPE = TypeRef.of(ArrayList.class); public static final TypeRef SET_TYPE = TypeRef.of(Set.class); public static final TypeRef HASHSET_TYPE = TypeRef.of(HashSet.class); public static final TypeRef MAP_TYPE = TypeRef.of(Map.class); + public static final TypeRef MAP_ENTRY_TYPE = TypeRef.of(Map.Entry.class); public static final TypeRef HASHMAP_TYPE = TypeRef.of(HashMap.class); public static final TypeRef OBJECT_TYPE = TypeRef.of(Object.class); diff --git a/java/fury-core/src/test/java/org/apache/fury/codegen/ExpressionVisitorTest.java b/java/fury-core/src/test/java/org/apache/fury/codegen/ExpressionVisitorTest.java index 3d6e9ef4f6..d9951ce9fd 100644 --- a/java/fury-core/src/test/java/org/apache/fury/codegen/ExpressionVisitorTest.java +++ b/java/fury-core/src/test/java/org/apache/fury/codegen/ExpressionVisitorTest.java @@ -19,8 +19,10 @@ package org.apache.fury.codegen; +import static org.apache.fury.type.TypeUtils.LIST_TYPE; import static org.testng.Assert.assertEquals; +import com.google.common.collect.ImmutableList; import java.lang.invoke.SerializedLambda; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -42,18 +44,15 @@ public void testTraverseExpression() throws InvocationTargetException, IllegalAc Expression.Reference ref = new Expression.Reference("a", TypeRef.of(ExpressionVisitorTest.class)); Expression e1 = new Expression.Invoke(ref, "testTraverseExpression"); - Literal start = Literal.ofInt(0); - Literal end = Literal.ofInt(10); - Literal step = Literal.ofInt(1); + Literal literal1 = Literal.ofInt(1); + Expression list = new Expression.StaticInvoke(ImmutableList.class, "of", LIST_TYPE, literal1); ExpressionVisitor.ExprHolder holder = ExpressionVisitor.ExprHolder.of("e1", e1, "e2", new Expression.ListExpression()); // FIXME ListExpression#add in lambda don't get executed, so ListExpression is the last expr. - Expression.ForLoop forLoop = - new Expression.ForLoop( - start, - end, - step, - expr -> ((Expression.ListExpression) (holder.get("e2"))).add(holder.get("e1"))); + Expression.ForEach forLoop = + new Expression.ForEach( + list, + (i, expr) -> ((Expression.ListExpression) (holder.get("e2"))).add(holder.get("e1"))); List expressions = new ArrayList<>(); new ExpressionVisitor() .traverseExpression(forLoop, exprSite -> expressions.add(exprSite.current)); @@ -69,7 +68,7 @@ public void testTraverseExpression() throws InvocationTargetException, IllegalAc // Traversal relies on getDeclaredFields(), nondeterministic order. Set expressionsSet = new HashSet<>(expressions); Set expressionsSet2 = - new HashSet<>(Arrays.asList(forLoop, e1, ref, exprHolder.get("e2"), end, start, step)); + new HashSet<>(Arrays.asList(forLoop, e1, ref, exprHolder.get("e2"), list, literal1)); assertEquals(expressionsSet, expressionsSet2); } } diff --git a/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java b/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java index c1af282610..2a29170753 100644 --- a/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java +++ b/java/fury-core/src/test/java/org/apache/fury/serializer/collection/MapSerializersTest.java @@ -53,6 +53,7 @@ import org.apache.fury.reflect.TypeRef; import org.apache.fury.serializer.Serializer; import org.apache.fury.serializer.collection.CollectionSerializersTest.TestEnum; +import org.apache.fury.test.bean.BeanB; import org.apache.fury.test.bean.Cyclic; import org.apache.fury.test.bean.MapFields; import org.apache.fury.type.GenericType; @@ -625,7 +626,6 @@ public void testObjectKeyValueChunk(boolean referenceTrackingConfig) { final Serializer serializer = fury.getSerializer(differentKeyAndValueTypeMap.getClass()); MapSerializers.HashMapSerializer mapSerializer = (MapSerializers.HashMapSerializer) serializer; - mapSerializer.setUseChunkSerialize(true); serDeCheck(fury, differentKeyAndValueTypeMap); } @@ -639,7 +639,6 @@ public void testObjectKeyValueBigChunk(boolean referenceTrackingConfig) { final Serializer serializer = fury.getSerializer(differentKeyAndValueTypeMap.getClass()); MapSerializers.HashMapSerializer mapSerializer = (MapSerializers.HashMapSerializer) serializer; - mapSerializer.setUseChunkSerialize(true); serDeCheck(fury, differentKeyAndValueTypeMap); } @@ -687,41 +686,6 @@ public void testMapFieldsChunkSerializer(boolean referenceTrackingConfig) { .requireClassRegistration(false) .build(); final MapFields mapFieldsObject = createBigMapFieldsObject(); - // hashmap - final Serializer serializer = fury.getSerializer(HashMap.class); - MapSerializers.HashMapSerializer mapSerializer = (MapSerializers.HashMapSerializer) serializer; - mapSerializer.setUseChunkSerialize(true); - - // LinkedHashMap - final Serializer serializer1 = fury.getSerializer(LinkedHashMap.class); - MapSerializers.LinkedHashMapSerializer linkedHashMapSerializer = - (MapSerializers.LinkedHashMapSerializer) serializer1; - linkedHashMapSerializer.setUseChunkSerialize(true); - - // TreeMap - final Serializer serializer2 = fury.getSerializer(TreeMap.class); - MapSerializers.SortedMapSerializer sortedMapSerializer = - (MapSerializers.SortedMapSerializer) serializer2; - sortedMapSerializer.setUseChunkSerialize(true); - - // ConcurrentHashMap - final Serializer serializer3 = fury.getSerializer(ConcurrentHashMap.class); - MapSerializers.ConcurrentHashMapSerializer concurrentHashMapSerializer = - (MapSerializers.ConcurrentHashMapSerializer) serializer3; - concurrentHashMapSerializer.setUseChunkSerialize(true); - - // ConcurrentSkipListMap - final Serializer serializer4 = - fury.getSerializer(ConcurrentSkipListMap.class); - MapSerializers.ConcurrentSkipListMapSerializer concurrentSkipListMapSerializer = - (MapSerializers.ConcurrentSkipListMapSerializer) serializer4; - concurrentSkipListMapSerializer.setUseChunkSerialize(true); - - final Serializer serializer5 = fury.getSerializer(EnumMap.class); - MapSerializers.EnumMapSerializer enumMapSerializer = - (MapSerializers.EnumMapSerializer) serializer5; - enumMapSerializer.setUseChunkSerialize(true); - serDeCheck(fury, mapFieldsObject); } @@ -745,4 +709,156 @@ private static Map createDifferentKeyAndValueTypeMap() { map.put("23", 900); return map; } + + @Data + public static class MapFieldStruct1 { + public Map map1; + public Map map2; + } + + @Test(dataProvider = "referenceTrackingConfig") + public void testMapFieldStructCodegen1(boolean referenceTrackingConfig) { + Fury fury = + Fury.builder() + .withRefTracking(referenceTrackingConfig) + .withCodegen(true) + .requireClassRegistration(false) + .build(); + MapFieldStruct1 struct1 = new MapFieldStruct1(); + struct1.map1 = ofHashMap(1, false, 2, true); + struct1.map2 = ofHashMap("k1", "v1", "k2", "v2"); + serDeCheck(fury, struct1); + } + + @Data + public static class MapFieldStruct2 { + public Map map1; + public Map map2; + public Map map3; + } + + @Test(dataProvider = "referenceTrackingConfig") + public void testMapFieldStructCodegen2(boolean referenceTrackingConfig) { + Fury fury = + Fury.builder() + .withRefTracking(referenceTrackingConfig) + .withCodegen(true) + .requireClassRegistration(false) + .build(); + MapFieldStruct2 struct1 = new MapFieldStruct2(); + struct1.map1 = ofHashMap(1, false, 2, true); + struct1.map2 = ofHashMap("k1", "v1", "k2", "v2"); + struct1.map3 = ofHashMap(1, "v1", 2, "v2"); + serDeCheck(fury, struct1); + } + + @Data + public static class MapFieldStruct3 { + public Map map1; + public Map map2; + public Map map3; + } + + @Test(dataProvider = "referenceTrackingConfig") + public void testMapFieldStructCodegen3(boolean referenceTrackingConfig) { + Fury fury = + Fury.builder() + .withRefTracking(referenceTrackingConfig) + .withCodegen(true) + .requireClassRegistration(false) + .build(); + MapFieldStruct3 struct1 = new MapFieldStruct3(); + BeanB beanB = BeanB.createBeanB(2); + struct1.map1 = ofHashMap(BeanB.createBeanB(2), BeanB.createBeanB(2)); + struct1.map2 = ofHashMap(BeanB.createBeanB(2), 1, beanB, beanB); + struct1.map3 = ofHashMap(1, beanB, 2, beanB, 3, BeanB.createBeanB(2)); + serDeCheck(fury, struct1); + } + + @Data + public static class NestedMapFieldStruct1 { + public Map> map1; + public Map> map2; + public Map> map3; + public Map>> map4; + } + + @Test(dataProvider = "referenceTrackingConfig") + public void testNestedMapFieldStructCodegen(boolean referenceTrackingConfig) { + Fury fury = + Fury.builder() + .withRefTracking(referenceTrackingConfig) + .withCodegen(true) + .requireClassRegistration(false) + .build(); + NestedMapFieldStruct1 struct1 = new NestedMapFieldStruct1(); + BeanB beanB = BeanB.createBeanB(2); + struct1.map1 = ofHashMap(1, ofHashMap("k1", "v1", "k2", "v2")); + struct1.map2 = ofHashMap("k1", ofHashMap("k1", 1, "k2", 2)); + struct1.map3 = ofHashMap(1, ofHashMap("k1", beanB, "k2", beanB, "k3", BeanB.createBeanB(1))); + struct1.map4 = + ofHashMap( + 2, ofHashMap(true, ofHashMap("k1", beanB, "k2", beanB, "k3", BeanB.createBeanB(1)))); + serDeCheck(fury, struct1); + } + + @Data + static class Wildcard { + public int c = 9; + public T t; + } + + @Data + private static class Wildcard1 { + public int c = 10; + public T t; + } + + @Data + public static class MapWildcardFieldStruct1 { + protected Map f0; + protected Map> f1; + protected Map> f2; + protected Map> f3; + protected Map> f4; + protected Map, Wildcard> f5; + protected Map> f6; + } + + @Test(dataProvider = "referenceTrackingConfig") + public void testWildcard(boolean referenceTrackingConfig) { + Fury fury = + Fury.builder() + .withRefTracking(referenceTrackingConfig) + .requireClassRegistration(false) + .build(); + MapWildcardFieldStruct1 struct = new MapWildcardFieldStruct1(); + struct.f0 = ofHashMap("k", 1); + struct.f1 = ofHashMap("k1", new Wildcard<>()); + struct.f2 = ofHashMap("k2", new Wildcard<>()); + struct.f3 = ofHashMap(new Wildcard<>(), new Wildcard<>()); + struct.f4 = ofHashMap(new Wildcard1<>(), new Wildcard1<>()); + struct.f5 = ofHashMap(new Wildcard1<>(), new Wildcard<>()); + struct.f5 = ofHashMap("k5", new Wildcard1<>()); + serDeCheck(fury, struct); + } + + @Data + public static class NestedListMap { + public List> map1; + public List> map2; + } + + @Test(dataProvider = "referenceTrackingConfig") + public void testNestedListMap(boolean referenceTrackingConfig) { + Fury fury = + Fury.builder() + .withRefTracking(referenceTrackingConfig) + .requireClassRegistration(false) + .build(); + NestedListMap o = new NestedListMap(); + o.map1 = ofArrayList(ofHashMap("k1", "v")); + o.map2 = ofArrayList(ofHashMap("k2", "2")); + serDeCheck(fury, o); + } } diff --git a/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java b/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java index 45d1f1baf3..5132a46938 100644 --- a/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java +++ b/java/fury-format/src/main/java/org/apache/fury/format/encoder/ArrayDataForEach.java @@ -27,6 +27,7 @@ import org.apache.fury.codegen.CodeGenerator; import org.apache.fury.codegen.CodegenContext; import org.apache.fury.codegen.Expression; +import org.apache.fury.codegen.Expression.AbstractExpression; import org.apache.fury.format.row.binary.BinaryArray; import org.apache.fury.format.row.binary.BinaryUtils; import org.apache.fury.reflect.TypeRef; @@ -41,7 +42,7 @@ * element action expression and null element action expression. */ @Internal -public class ArrayDataForEach implements Expression { +public class ArrayDataForEach extends AbstractExpression { private final Expression inputArrayData; private final String accessMethod; private final TypeRef elemType; @@ -71,6 +72,7 @@ public ArrayDataForEach( TypeRef elemType, SerializableBiFunction notNullAction, SerializableFunction nullAction) { + super(inputArrayData); Preconditions.checkArgument(getRawType(inputArrayData.type()) == BinaryArray.class); this.inputArrayData = inputArrayData; this.accessMethod = BinaryUtils.getElemAccessMethodName(elemType);