diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index 5ad083d69e..a7916400ae 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -352,7 +352,7 @@ public MemoryBuffer serialize(MemoryBuffer buffer, Object obj, BufferCallback ca if (writeContext.getDepth() > 0) { throwDepthSerializationException(); } - writeContext.writeRef(obj); + writeContext.writeRootRef(obj); return buffer; } catch (Throwable t) { throw processSerializationError(t); @@ -512,7 +512,7 @@ public Object deserialize(MemoryBuffer buffer, Iterable outOfBandB if (readContext.getDepth() > 0) { throwDepthDeserializationException(); } - return readContext.readRef(); + return readContext.readRootRef(); } finally { jitContext.unlock(); } diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java index c431f7ec7c..c1f281ed3b 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java @@ -138,6 +138,7 @@ import org.apache.fory.serializer.ReplaceResolveSerializer; import org.apache.fory.serializer.Serializer; import org.apache.fory.serializer.StringSerializer; +import org.apache.fory.serializer.UnknownClassSerializers; import org.apache.fory.serializer.collection.CollectionFlags; import org.apache.fory.serializer.collection.CollectionLikeSerializer; import org.apache.fory.serializer.collection.CollectionSerializer; @@ -701,6 +702,18 @@ private Expression serializeForNotNullObjectForField( Expression inputObject, Expression buffer, Descriptor descriptor, Expression serializer) { TypeRef typeRef = descriptor.getTypeRef(); Class clz = getRawType(typeRef); + if (isEnumType(clz)) { + Expression enumSerializer = serializer == null ? getSerializerForField(clz) : serializer; + if (hasEnumValueMethods(enumSerializer)) { + Class serializerClass = getRawType(enumSerializer.type()); + Expression value = + EnumSerializer.class.isAssignableFrom(serializerClass) + ? cast(inputObject, TypeRef.of(Enum.class)) + : inputObject; + return new Invoke(enumSerializer, "writeValue", writeContextRef(), buffer, value); + } + return new Invoke(enumSerializer, writeMethodName, writeContextRef(), inputObject); + } if (serializer != null) { return new Invoke(serializer, writeMethodName, writeContextRef(), inputObject); } @@ -717,6 +730,17 @@ private Expression getSerializerForField(Class cls) { return getOrCreateSerializer(cls, true); } + private static boolean isEnumType(Class cls) { + return cls != Enum.class && Enum.class.isAssignableFrom(cls); + } + + private static boolean hasEnumValueMethods(Expression serializer) { + Class serializerClass = getRawType(serializer.type()); + // UnknownEnumSerializer is enum-shaped but does not extend EnumSerializer. + return EnumSerializer.class.isAssignableFrom(serializerClass) + || UnknownClassSerializers.UnknownEnumSerializer.class.isAssignableFrom(serializerClass); + } + protected Expression serializeForNullable( Expression inputObject, Expression buffer, TypeRef typeRef, boolean nullable) { return serializeForNullable(inputObject, buffer, typeRef, null, false, nullable); @@ -885,6 +909,7 @@ protected Expression writeForNotNullNonFinalObject( Class clz = getRawType(typeRef); Expression clsExpr = new Invoke(inputObject, "getClass", "cls", CLASS_TYPE); ListExpression writeClassAndObject = new ListExpression(); + Expression exactClassWrite = exactClassWrite(inputObject, buffer, clz); Tuple2 classInfoRef = addTypeInfoField(clz); Expression classInfo = classInfoRef.f0; if (classInfoRef.f1) { @@ -904,12 +929,25 @@ protected Expression writeForNotNullNonFinalObject( PRIMITIVE_VOID_TYPE, writeContextRef(), inputObject)); + Expression write = + exactClassWrite == null + ? writeClassAndObject + : new If(eq(clsExpr, getClassExpr(clz)), exactClassWrite, writeClassAndObject, false); return invokeGenerated( - ctx, - writeCutPoints(buffer, inputObject), - writeClassAndObject, - "writeClassAndObject", - false); + ctx, writeCutPoints(buffer, inputObject), write, "writeClassAndObject", false); + } + + private Expression exactClassWrite(Expression inputObject, Expression buffer, Class clz) { + if (clz.isInterface() || Modifier.isAbstract(clz.getModifiers())) { + return null; + } + Reference typeInfo = addExactTypeInfoField(clz); + Expression serializer = getOrCreateSerializer(clz); + return new ListExpression( + typeResolver( + r -> r.writeExactClassExpr(typeResolverRef, writeContextRef(), buffer, typeInfo, clz)), + new Invoke( + serializer, writeMethodName, PRIMITIVE_VOID_TYPE, writeContextRef(), inputObject)); } protected Expression writeTypeInfo( @@ -1128,6 +1166,21 @@ protected Tuple2 addTypeInfoField(Class cls) { return classInfoRef; } + protected Reference addExactTypeInfoField(Class cls) { + String key = "exactClassInfo:" + cls; + Reference reference = (Reference) sharedFieldMap.get(key); + if (reference != null) { + return reference; + } + Expression classInfoExpr = + inlineInvoke(typeResolverRef, "getTypeInfo", classInfoTypeRef, getClassExpr(cls)); + String name = ctx.newName(ctx.newName(cls) + "ExactTypeInfo"); + ctx.addField(true, ctx.type(TypeInfo.class), name, classInfoExpr); + reference = fieldRef(name, classInfoTypeRef); + sharedFieldMap.put(key, reference); + return reference; + } + protected Reference addTypeInfoHolderField(Class cls) { // Final type need to write classinfo when meta share enabled. String key; @@ -1334,11 +1387,18 @@ protected Expression writeCollectionData( builder.add( writeContainerElements(elementType, true, null, null, buffer, collection, size)); } else { + Expression declSameNoNull = + eq(flags, ofInt(CollectionFlags.DECL_SAME_TYPE_NOT_HAS_NULL), "declSameNoNull"); Literal hasNullFlag = ofInt(CollectionFlags.HAS_NULL); Expression hasNull = eq(new BitAnd(flags, hasNullFlag), hasNullFlag, "hasNull"); builder.add( + declSameNoNull, hasNull, - writeContainerElements(elementType, false, null, hasNull, buffer, collection, size)); + new If( + declSameNoNull, + writeContainerElements(elementType, false, null, null, buffer, collection, size), + writeContainerElements(elementType, false, null, hasNull, buffer, collection, size), + false)); } } else { Literal flag = ofInt(CollectionFlags.IS_SAME_TYPE); @@ -1398,9 +1458,28 @@ protected Expression writeCollectionData( differentTypeWrite); } else { // if declared elem type don't track ref, all elements must not write ref. + Expression declSameNoNull = + eq(flags, ofInt(CollectionFlags.DECL_SAME_TYPE_NOT_HAS_NULL), "declSameNoNull"); Literal hasNullFlag = ofInt(CollectionFlags.HAS_NULL); Expression hasNull = eq(new BitAnd(flags, hasNullFlag), hasNullFlag, "hasNull"); - builder.add(hasNull); + builder.add(declSameNoNull, hasNull); + Expression declaredNoNullWrite = null; + if (maybeDecl) { + declaredNoNullWrite = + invokeGenerated( + ctx, + writeCutPoints(buffer, collection, size), + writeContainerElements( + elementType, + false, + cast(getOrCreateSerializer(elemClass), serializerType), + null, + buffer, + collection, + size), + "declSameNoNullWrite", + false); + } ListExpression writeBuilder = new ListExpression(elemSerializer); writeBuilder.add( writeContainerElements( @@ -1415,6 +1494,9 @@ protected Expression writeCollectionData( invokeGenerated(ctx, cutPoint, writeBuilder, "sameElementClassWrite", false), writeContainerElements( elementType, false, null, hasNull, buffer, collection, size)); + if (declaredNoNullWrite != null) { + action = new If(declSameNoNull, declaredNoNullWrite, action, false); + } } builder.add(action); } @@ -2344,6 +2426,16 @@ private Expression deserializeForNotNullForField( return StringSerializer.readStringExpr( getOrCreateStringSerializer(), buffer, config.compressString()); } + if (isEnumType(cls)) { + Expression enumSerializer = serializer == null ? getSerializerForField(cls) : serializer; + if (!hasEnumValueMethods(enumSerializer)) { + return cast( + new Invoke(enumSerializer, readMethodName, OBJECT_TYPE, readContextRef()), + TypeRef.of(Enum.class)); + } + return new Invoke( + enumSerializer, "readValue", TypeRef.of(Enum.class), readContextRef(), buffer); + } Expression obj; if (usesPrimitiveListArrayProtocol(descriptor)) { serializer = getPrimitiveListArraySerializer(cls); @@ -2595,7 +2687,7 @@ protected Expression readForNotNullNonFinal( classInfo = readTypeInfo(getRawType(typeRef), buffer); } } else { - classInfo = readTypeInfo(getRawType(typeRef), buffer); + classInfo = readTypeInfo(rawType, buffer); } serializer = inlineInvoke(classInfo, "getSerializer", SERIALIZER_TYPE); } @@ -2638,16 +2730,18 @@ protected Expression deserializeForCollection( Expression collection = new Invoke(serializer, "newCollection", COLLECTION_TYPE, readContextRef); Expression size = new Invoke(serializer, "getAndClearNumElements", "size", PRIMITIVE_INT_TYPE); - // if add branch by `ArrayList`, generated code will be > 325 bytes. - // and List#add is more likely be inlined if there is only one subclass. Expression hookRead = readCollectionCodegen(buffer, collection, size, elementType); hookRead = new Invoke(serializer, "onCollectionRead", OBJECT_TYPE, hookRead); - Expression action = - new If( - supportHook, - new ListExpression(collection, hookRead), - read(serializer, buffer, OBJECT_TYPE), + Expression fallbackAction = read(serializer, buffer, OBJECT_TYPE); + Expression fallbackRead = + invokeGenerated( + ctx, + readCutPoints(buffer, serializer), + new ListExpression(fallbackAction, new Return(fallbackAction)), + "readCollectionFallback", false); + Expression action = + new If(supportHook, new ListExpression(collection, hookRead), fallbackRead, false); if (invokeHint != null && invokeHint.genNewMethod) { invokeHint.add(buffer); invokeHint.add(readContextRef()); @@ -2664,8 +2758,12 @@ protected Expression deserializeForCollection( protected Expression readCollectionCodegen( Expression buffer, Expression collection, Expression size, TypeRef elementType) { ListExpression builder = new ListExpression(); - Invoke flags = new Invoke(buffer, "readByte", "flags", PRIMITIVE_INT_TYPE, false); - builder.add(flags); + Expression readerIndex = new Invoke(buffer, "readerIndex", "readerIndex", PRIMITIVE_INT_TYPE); + Expression flags = + cast( + new Invoke(buffer, "_unsafeGetByte", PRIMITIVE_BYTE_TYPE, readerIndex), + PRIMITIVE_INT_TYPE); + builder.add(readerIndex, flags, new Invoke(buffer, "_increaseReaderIndexUnsafe", ofInt(1))); Class elemClass = TypeUtils.getRawType(elementType); walkPath.add(elementType.toString()); boolean finalType = isMonomorphic(elemClass); diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java index 7bfd4898c9..8e5083b6ca 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/CodecBuilder.java @@ -518,6 +518,8 @@ private Expression varHandleSetField(Expression bean, Descriptor descriptor, Exp if (descriptor.getTypeRef().isPrimitive()) { Preconditions.checkArgument(getRawType(value.type()) == getRawType(fieldType)); } + // Janino cannot compile VarHandle's signature-polymorphic access modes directly. Keep generated + // serializers on typed helper methods even though the VarHandle itself is a static final field. return new StaticInvoke( varHandleSupportClass(), varHandleSetMethod(fieldType), @@ -609,6 +611,7 @@ protected Reference getOrCreateField( private Expression varHandleGetField(Expression inputObject, Descriptor descriptor) { TypeRef returnType = descriptor.getTypeRef().isPrimitive() ? descriptor.getTypeRef() : OBJECT_TYPE; + // See varHandleSetField: direct VarHandle access-mode calls are not valid Janino output. Expression getValue = new StaticInvoke( varHandleSupportClass(), diff --git a/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java b/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java index e22cd2b38c..b467361ac0 100644 --- a/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/builder/ObjectCodecBuilder.java @@ -799,9 +799,10 @@ public Expression buildDecodeExpression() { Expression bean; if (!isRecord) { bean = newBean(); - Expression referenceObject = invokeReadContext("reference", bean); expressions.add(bean); - expressions.add(referenceObject); + if (typeResolver.trackingRef()) { + expressions.add(invokeReadContext("reference", bean)); + } } else { if (recordCtrAccessible) { bean = new FieldsCollector(); diff --git a/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java b/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java index 6dca2e503a..b1000161b3 100644 --- a/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java +++ b/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java @@ -55,9 +55,11 @@ public final class ReadContext { private final Generics generics; private final TypeResolver typeResolver; private final RefReader refReader; + private final TypeInfoHolder rootTypeInfoHolder; private final MetaStringReader metaStringReader; private final StringSerializer stringSerializer; private final boolean crossLanguage; + private final boolean trackingRef; private final boolean compressInt; private final Int64Encoding longEncoding; private final int maxDepth; @@ -86,9 +88,11 @@ public ReadContext( this.generics = generics; this.typeResolver = typeResolver; this.refReader = refReader; + rootTypeInfoHolder = typeResolver.nilTypeInfoHolder(); this.metaStringReader = metaStringReader; stringSerializer = (StringSerializer) typeResolver.getSerializer(String.class); crossLanguage = config.isXlang(); + trackingRef = config.trackingRef(); compressInt = config.compressInt(); longEncoding = config.longEncoding(); maxDepth = config.maxDepth(); @@ -578,6 +582,20 @@ public T readRef(Serializer serializer) { return (T) readNonRef(serializer); } + /** Reads the root object for one deserialization operation. */ + public Object readRootRef() { + if (trackingRef) { + return readRef(rootTypeInfoHolder); + } + MemoryBuffer buffer = this.buffer; + int headFlag = buffer.readByte(); + if (headFlag >= Fory.NOT_NULL_VALUE_FLAG) { + TypeInfo typeInfo = typeResolver.readTypeInfo(this, rootTypeInfoHolder); + return readNonRef(typeInfo); + } + return null; + } + /** Reads a non-null, first-seen object together with its type metadata. */ public Object readNonRef() { TypeInfo typeInfo = typeResolver.readTypeInfo(this); diff --git a/java/fory-core/src/main/java/org/apache/fory/context/WriteContext.java b/java/fory-core/src/main/java/org/apache/fory/context/WriteContext.java index ebab5330b0..bf74c0a67a 100644 --- a/java/fory-core/src/main/java/org/apache/fory/context/WriteContext.java +++ b/java/fory-core/src/main/java/org/apache/fory/context/WriteContext.java @@ -56,9 +56,11 @@ public final class WriteContext { private final Generics generics; private final TypeResolver typeResolver; private final RefWriter refWriter; + private final TypeInfoHolder rootTypeInfoHolder; private final MetaStringWriter metaStringWriter; private final StringSerializer stringSerializer; private final boolean crossLanguage; + private final boolean trackingRef; private final boolean compressInt; private final Int64Encoding longEncoding; private final boolean forVirtualThread; @@ -85,9 +87,11 @@ public WriteContext( this.generics = generics; this.typeResolver = typeResolver; this.refWriter = refWriter; + rootTypeInfoHolder = typeResolver.nilTypeInfoHolder(); this.metaStringWriter = metaStringWriter; stringSerializer = (StringSerializer) typeResolver.getSerializer(String.class); crossLanguage = config.isXlang(); + trackingRef = config.trackingRef(); compressInt = config.compressInt(); longEncoding = config.longEncoding(); forVirtualThread = config.forVirtualThread(); @@ -527,6 +531,30 @@ public void writeRef(T obj, Serializer serializer) { } } + /** Writes the root object for one serialization operation. */ + public void writeRootRef(Object obj) { + if (trackingRef) { + writeRef(obj, rootTypeInfoHolder); + return; + } + MemoryBuffer buffer = this.buffer; + if (obj == null) { + buffer.writeByte(Fory.NULL_FLAG); + return; + } + buffer.writeByte(Fory.NOT_NULL_VALUE_FLAG); + TypeResolver resolver = typeResolver; + TypeInfo typeInfo = resolver.getTypeInfo(obj.getClass(), rootTypeInfoHolder); + if (crossLanguage && typeInfo.getType() == UnknownStruct.class) { + depth++; + typeInfo.getSerializer().write(this, obj); + depth--; + return; + } + resolver.writeTypeInfo(this, typeInfo); + writeData(typeInfo, obj); + } + /** * Writes a non-null, first-seen object together with its type metadata. * diff --git a/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java b/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java index 94dd961889..c0f002ca98 100644 --- a/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java +++ b/java/fory-core/src/main/java/org/apache/fory/memory/MemoryBuffer.java @@ -962,7 +962,9 @@ public void writeByte(byte value) { } else { final int writerIdx = writerIndex; final int newIdx = writerIdx + 1; - ensure(newIdx); + if (newIdx > size) { + globalAllocator.grow(this, newIdx); + } final long pos = address + writerIdx; UNSAFE.putByte(heapMemory, pos, value); writerIndex = newIdx; diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java index b104985527..35417977f0 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java @@ -557,6 +557,27 @@ public final void writeTypeInfo(WriteContext writeContext, TypeInfo typeInfo) { } } + // Generated exact-class branches call this only after code generation resolves the TypeInfo to a + // compatible struct id. The wire body stays in writeSharedClassMeta; this helper only skips the + // generic type-id switch. + @Internal + public final void writeCompatibleTypeInfo( + WriteContext writeContext, MemoryBuffer buffer, TypeInfo typeInfo) { + assert isCompatibleStructTypeId(typeInfo.typeId); + buffer.writeUInt8(typeInfo.typeId); + writeSharedClassMeta(writeContext, buffer, typeInfo); + } + + private static boolean isCompatibleStructTypeId(int typeId) { + switch (typeId) { + case Types.COMPATIBLE_STRUCT: + case Types.NAMED_COMPATIBLE_STRUCT: + return true; + default: + return false; + } + } + static int toUserTypeId(long userTypeId) { Preconditions.checkArgument( userTypeId >= 0 && userTypeId <= MAX_USER_TYPE_ID, @@ -576,6 +597,21 @@ public Expression writeClassExpr( return new Invoke(classResolverRef, "writeTypeInfo", buffer, classInfo); } + // Note: Thread safe for jit thread to call. + public Expression writeExactClassExpr( + Expression classResolverRef, + Expression writeContext, + Expression buffer, + Expression classInfo, + Class cls) { + TypeInfo typeInfo = getTypeInfo(cls); + if (isCompatibleStructTypeId(typeInfo.typeId)) { + return new Invoke( + classResolverRef, "writeCompatibleTypeInfo", writeContext, buffer, classInfo); + } + return writeClassExpr(classResolverRef, writeContext, classInfo); + } + /** * Writes shared class metadata using the meta-share protocol. Protocol: If class already written, * writes {@code (index << 1) | 1} (reference). If new class, writes {@code (index << 1)} followed @@ -585,6 +621,11 @@ public Expression writeClassExpr( */ protected final void writeSharedClassMeta(WriteContext writeContext, TypeInfo typeInfo) { MemoryBuffer buffer = writeContext.getBuffer(); + writeSharedClassMeta(writeContext, buffer, typeInfo); + } + + private void writeSharedClassMeta( + WriteContext writeContext, MemoryBuffer buffer, TypeInfo typeInfo) { MetaWriteContext metaWriteContext = writeContext.getMetaWriteContext(); assert metaWriteContext != null : SET_META_WRITE_CONTEXT_MSG; IdentityObjectIntMap> classMap = metaWriteContext.classMap; @@ -626,7 +667,7 @@ public final TypeInfo readTypeInfo(ReadContext readContext) { case Types.STRUCT: case Types.EXT: case Types.TYPED_UNION: - typeInfo = Objects.requireNonNull(userTypeIdToTypeInfo.get(buffer.readVarUInt32())); + typeInfo = readRegisteredTypeInfo(typeId, buffer.readVarUInt32(), typeInfoCache); break; case Types.COMPATIBLE_STRUCT: case Types.NAMED_COMPATIBLE_STRUCT: @@ -671,7 +712,7 @@ public final TypeInfo readTypeInfo(ReadContext readContext, Class targetClass case Types.STRUCT: case Types.EXT: case Types.TYPED_UNION: - typeInfo = Objects.requireNonNull(userTypeIdToTypeInfo.get(buffer.readVarUInt32())); + typeInfo = readRegisteredTypeInfo(typeId, buffer.readVarUInt32(), typeInfoCache); break; case Types.COMPATIBLE_STRUCT: case Types.NAMED_COMPATIBLE_STRUCT: @@ -721,7 +762,7 @@ public final TypeInfo readTypeInfo(ReadContext readContext, TypeInfo typeInfoCac case Types.STRUCT: case Types.EXT: case Types.TYPED_UNION: - typeInfo = Objects.requireNonNull(userTypeIdToTypeInfo.get(buffer.readVarUInt32())); + typeInfo = readRegisteredTypeInfo(typeId, buffer.readVarUInt32(), typeInfoCache); break; case Types.COMPATIBLE_STRUCT: case Types.NAMED_COMPATIBLE_STRUCT: @@ -770,11 +811,13 @@ public final TypeInfo readTypeInfo(ReadContext readContext, TypeInfoHolder class case Types.STRUCT: case Types.EXT: case Types.TYPED_UNION: - typeInfo = Objects.requireNonNull(userTypeIdToTypeInfo.get(buffer.readVarUInt32())); + typeInfo = readRegisteredTypeInfo(typeId, buffer.readVarUInt32(), classInfoHolder.typeInfo); + updateCache = typeInfo != classInfoHolder.typeInfo; break; case Types.COMPATIBLE_STRUCT: case Types.NAMED_COMPATIBLE_STRUCT: - typeInfo = readSharedClassTypeInfo(readContext, null); + typeInfo = readSharedClassTypeInfo(readContext, null, classInfoHolder.typeInfo); + updateCache = typeInfo != classInfoHolder.typeInfo; break; case Types.NAMED_ENUM: case Types.NAMED_STRUCT: @@ -784,7 +827,8 @@ public final TypeInfo readTypeInfo(ReadContext readContext, TypeInfoHolder class typeInfo = readTypeInfoFromBytes(readContext, classInfoHolder.typeInfo, typeId); updateCache = true; } else { - typeInfo = readSharedClassTypeInfo(readContext, null); + typeInfo = readSharedClassTypeInfo(readContext, null, classInfoHolder.typeInfo); + updateCache = typeInfo != classInfoHolder.typeInfo; } break; case Types.LIST: @@ -805,6 +849,14 @@ public final TypeInfo readTypeInfo(ReadContext readContext, TypeInfoHolder class return typeInfo; } + private TypeInfo readRegisteredTypeInfo(int typeId, int userTypeId, TypeInfo cachedTypeInfo) { + TypeInfo typeInfo = cachedTypeInfo; + if (typeInfo == null || typeInfo.typeId != typeId || typeInfo.userTypeId != userTypeId) { + typeInfo = Objects.requireNonNull(userTypeIdToTypeInfo.get(userTypeId)); + } + return typeInfo; + } + /** * Read class info using the provided cache. Returns cached TypeInfo if the namespace and type * name bytes match. @@ -850,6 +902,10 @@ protected final TypeInfo readTypeInfoFromBytes( public final TypeInfo readSharedClassMeta(ReadContext readContext, Class targetClass) { TypeInfo typeInfo = readSharedClassTypeInfo(readContext, targetClass); + return adaptSharedClassTarget(typeInfo, targetClass); + } + + private TypeInfo adaptSharedClassTarget(TypeInfo typeInfo, Class targetClass) { Class readClass = typeInfo.getType(); if (targetClass != readClass) { return getTargetTypeInfo(typeInfo, targetClass); @@ -858,6 +914,11 @@ public final TypeInfo readSharedClassMeta(ReadContext readContext, Class targ } private TypeInfo readSharedClassTypeInfo(ReadContext readContext, Class targetClass) { + return readSharedClassTypeInfo(readContext, targetClass, null); + } + + private TypeInfo readSharedClassTypeInfo( + ReadContext readContext, Class targetClass, TypeInfo cachedTypeInfo) { MemoryBuffer buffer = readContext.getBuffer(); MetaReadContext metaReadContext = readContext.getMetaReadContext(); assert metaReadContext != null : SET_META_READ_CONTEXT_MSG; @@ -875,7 +936,14 @@ private TypeInfo readSharedClassTypeInfo(ReadContext readContext, Class targe // body/hash/schema-limit/exact-local checks here; the header-miss path owns them before // cache publish. long id = buffer.readInt64(); - typeInfo = extRegistry.typeInfoByTypeDefId.get(id); + TypeDef cachedTypeDef = cachedTypeInfo == null ? null : cachedTypeInfo.getTypeDef(); + // A field-local cache hit is valid only when the cached TypeInfo carries the exact checked + // TypeDef id that was parsed and accepted earlier by this resolver. + if (cachedTypeDef != null && cachedTypeDef.getId() == id) { + typeInfo = cachedTypeInfo; + } else { + typeInfo = extRegistry.typeInfoByTypeDefId.get(id); + } if (typeInfo != null) { TypeDef.skipTypeDef(buffer, id); } else { diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java index f07098d6c1..7e7e1d27bc 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/EnumSerializer.java @@ -26,11 +26,13 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import org.apache.fory.annotation.CodegenInvoke; import org.apache.fory.annotation.ForyEnumId; import org.apache.fory.collection.LongMap; import org.apache.fory.config.Config; import org.apache.fory.context.ReadContext; import org.apache.fory.context.WriteContext; +import org.apache.fory.memory.MemoryBuffer; import org.apache.fory.util.Preconditions; @SuppressWarnings("rawtypes") @@ -65,15 +67,25 @@ public EnumSerializer(Config config, Class cls) { @Override public void write(WriteContext writeContext, Enum value) { + writeValue(writeContext, writeContext.getBuffer(), value); + } + + @CodegenInvoke + public final void writeValue(WriteContext writeContext, MemoryBuffer buffer, Enum value) { if (!config.isXlang() && config.serializeEnumByName()) { writeContext.writeString(value.name()); } else { - writeContext.getBuffer().writeVarUInt32Small7(tagByOrdinal[value.ordinal()]); + buffer.writeVarUInt32Small7(tagByOrdinal[value.ordinal()]); } } @Override public Enum read(ReadContext readContext) { + return readValue(readContext, readContext.getBuffer()); + } + + @CodegenInvoke + public final Enum readValue(ReadContext readContext, MemoryBuffer buffer) { if (!config.isXlang() && config.serializeEnumByName()) { String name = readContext.readString(); Enum e = stringToEnum.get(name); @@ -82,7 +94,7 @@ public Enum read(ReadContext readContext) { } return handleUnknownEnumValue(name); } else { - int tag = readContext.getBuffer().readVarUInt32Small7(); + int tag = buffer.readVarUInt32Small7(); Enum value = null; if (enumConstantByTagArray != null && tag < enumConstantByTagArray.length) { value = enumConstantByTagArray[tag]; diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java b/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java index bb3d93f22a..2ad935101f 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java @@ -537,16 +537,19 @@ public static void writeBytesString(MemoryBuffer buffer, byte coder, byte[] byte int bytesLen = bytes.length; long header = ((long) bytesLen << 2) | coder; int writerIndex = buffer.writerIndex(); - // The `ensure` ensure next operations are safe without bound checks, - // and inner heap buffer doesn't change. - buffer.ensure(writerIndex + 9 + bytesLen); // 1 byte coder + varint max 8 bytes + buffer.ensure(writerIndex + 9 + bytesLen); final byte[] targetArray = buffer.getHeapMemory(); if (targetArray != null) { - // Some JDK11 Unsafe.copyMemory will `copyMemoryChecks`, and - // jvm doesn't eliminate well in some jdk. final int targetIndex = buffer._unsafeHeapWriterIndex(); int arrIndex = targetIndex; - arrIndex += LittleEndian.putVarUint36Small(targetArray, arrIndex, header); + if (header >>> 7 == 0) { + targetArray[arrIndex++] = (byte) header; + } else if (header >>> 14 == 0) { + targetArray[arrIndex++] = (byte) ((header & 0x7F) | 0x80); + targetArray[arrIndex++] = (byte) (header >>> 7); + } else { + arrIndex += LittleEndian.putVarUint36Small(targetArray, arrIndex, header); + } writerIndex += arrIndex - targetIndex; System.arraycopy(bytes, 0, targetArray, arrIndex, bytesLen); } else { @@ -644,7 +647,7 @@ public byte[] readBytesUnCompressedUTF16(MemoryBuffer buffer, int numBytes) { byte[] heapMemory = buffer.getHeapMemory(); if (heapMemory != null) { final int arrIndex = buffer._unsafeHeapReaderIndex(); - buffer.increaseReaderIndex(numBytes); + buffer._increaseReaderIndexUnsafe(numBytes); bytes = new byte[numBytes]; System.arraycopy(heapMemory, arrIndex, bytes, 0, numBytes); } else { diff --git a/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java b/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java index ef03486e11..1356d80a35 100644 --- a/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java +++ b/java/fory-core/src/main/java/org/apache/fory/serializer/UnknownClassSerializers.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.fory.annotation.CodegenInvoke; import org.apache.fory.collection.IdentityObjectIntMap; import org.apache.fory.collection.LongMap; import org.apache.fory.collection.MapEntry; @@ -294,21 +295,32 @@ public UnknownEnumSerializer(TypeResolver typeResolver) { @Override public void write(WriteContext writeContext, UnknownEnum value) { + writeValue(writeContext, writeContext.getBuffer(), value); + } + + @CodegenInvoke + public final void writeValue( + WriteContext writeContext, MemoryBuffer buffer, UnknownEnum value) { if (!config.isXlang() && config.serializeEnumByName()) { writeContext.writeString(value.name()); } else { - writeContext.getBuffer().writeVarUInt32Small7(value.ordinal()); + buffer.writeVarUInt32Small7(value.ordinal()); } } @Override public UnknownEnum read(ReadContext readContext) { + return readValue(readContext, readContext.getBuffer()); + } + + @CodegenInvoke + public final UnknownEnum readValue(ReadContext readContext, MemoryBuffer buffer) { if (!config.isXlang() && config.serializeEnumByName()) { readContext.readString(); return UnknownEnum.UNKNOWN; } - int ordinal = readContext.getBuffer().readVarUInt32Small7(); + int ordinal = buffer.readVarUInt32Small7(); if (ordinal >= enumConstants.length) { return UnknownEnum.UNKNOWN; } diff --git a/java/fory-core/src/main/java25/org/apache/fory/memory/MemoryBuffer.java b/java/fory-core/src/main/java25/org/apache/fory/memory/MemoryBuffer.java index 7fbf021ff5..52c4d0ce50 100644 --- a/java/fory-core/src/main/java25/org/apache/fory/memory/MemoryBuffer.java +++ b/java/fory-core/src/main/java25/org/apache/fory/memory/MemoryBuffer.java @@ -1108,7 +1108,9 @@ public void writeUInt8(int value) { public void writeByte(byte value) { final int writerIdx = writerIndex; final int newIdx = writerIdx + 1; - ensure(newIdx); + if (newIdx > size) { + globalAllocator.grow(this, newIdx); + } final long pos = address + writerIdx; storeByte(pos, value); writerIndex = newIdx; diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java index 756c00e554..c910414959 100644 --- a/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java @@ -60,6 +60,41 @@ public void testRejectOddUtf16ByteSize() { Assert.assertThrows(IllegalArgumentException.class, () -> serializer.readString(buffer)); } + @Test + public void testBytesStringWire() { + if (!stringValueIsBytes()) { + throw new SkipException("Skip when string value is not byte[]"); + } + String[] values = { + "", + "a", + StringUtils.random(31), + StringUtils.random(40), + StringUtils.random(5000), + "你好", + "abc你好" + }; + for (String value : values) { + int capacity = Math.max(64, value.length() * 8 + 64); + MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(capacity); + StringSerializer.writeBytesString(buffer, value); + Assert.assertEquals( + readJDK11String(MemoryBuffer.fromByteArray(buffer.getBytes(0, buffer.writerIndex()))), + value); + } + } + + private static boolean stringValueIsBytes() { + try { + Field valueIsBytesField = + StringSerializer.class.getDeclaredField("STRING_VALUE_FIELD_IS_BYTES"); + valueIsBytesField.setAccessible(true); + return (boolean) valueIsBytesField.get(null); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + @Test public void testJavaStringZeroCopy() { if (JdkVersion.MAJOR_VERSION >= 17) {