Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
840000b
perf(java): improve compatible mode performance
chaokunyang Jun 25, 2026
157aba0
perf(java): improve compatible serialization paths
chaokunyang Jun 25, 2026
232c6ce
perf(java): clean compatible mode optimizations
chaokunyang Jun 25, 2026
31e048c
perf(java): speed compatible type info writes
chaokunyang Jun 25, 2026
496da59
perf(java): unify compatible string writes
chaokunyang Jun 26, 2026
573cd38
fix(java): handle unknown enum codegen
chaokunyang Jun 26, 2026
b3117d0
docs: format generated code example
chaokunyang Jun 29, 2026
20dd260
fix(java): check generated collection flag reads
chaokunyang Jun 29, 2026
5c909ff
perf(java): remove nullable type info cache check
chaokunyang Jun 29, 2026
88535b1
refactor(java): inline shared class target adaptation
chaokunyang Jun 29, 2026
5034994
perf(java): update type info holder after resolution
chaokunyang Jun 29, 2026
9ea6468
fix(java): avoid null type info cache for union reads
chaokunyang Jun 29, 2026
6219f2e
perf(java): index type info cache by depth
chaokunyang Jun 29, 2026
a98ba64
perf(java): index writer type info cache by depth
chaokunyang Jun 29, 2026
7b0e285
revert(java): remove hidden nestmate codegen path
chaokunyang Jun 29, 2026
3779459
ci: use allowlisted graalvm setup action
chaokunyang Jun 29, 2026
698c77d
ci: document apache action allowlist reference
chaokunyang Jun 29, 2026
ba97b11
fix(java): update json codegen class loading
chaokunyang Jun 29, 2026
c87fe40
fix(java): keep generated serializer paths resolved
chaokunyang Jun 29, 2026
603b7fc
test(java): align jpms codegen final-field assertion
chaokunyang Jun 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions java/fory-core/src/main/java/org/apache/fory/Fory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -512,7 +512,7 @@ public Object deserialize(MemoryBuffer buffer, Iterable<MemoryBuffer> outOfBandB
if (readContext.getDepth() > 0) {
throwDepthDeserializationException();
}
return readContext.readRef();
return readContext.readRootRef();
} finally {
jitContext.unlock();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,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 =
cast(
serializer == null ? getSerializerForField(clz) : serializer,
TypeRef.of(EnumSerializer.class));
return new Invoke(
enumSerializer,
"writeValue",
writeContextRef(),
buffer,
cast(inputObject, TypeRef.of(Enum.class)));
}
if (serializer != null) {
return new Invoke(serializer, writeMethodName, writeContextRef(), inputObject);
}
Expand All @@ -717,6 +729,10 @@ private Expression getSerializerForField(Class<?> cls) {
return getOrCreateSerializer(cls, true);
}

private static boolean isEnumType(Class<?> cls) {
return cls != Enum.class && Enum.class.isAssignableFrom(cls);
}

protected Expression serializeForNullable(
Expression inputObject, Expression buffer, TypeRef<?> typeRef, boolean nullable) {
return serializeForNullable(inputObject, buffer, typeRef, null, false, nullable);
Expand Down Expand Up @@ -885,6 +901,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<Reference, Boolean> classInfoRef = addTypeInfoField(clz);
Expression classInfo = classInfoRef.f0;
if (classInfoRef.f1) {
Expand All @@ -904,12 +921,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(
Expand Down Expand Up @@ -1128,6 +1158,21 @@ protected Tuple2<Reference, Boolean> 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;
Expand Down Expand Up @@ -1175,6 +1220,17 @@ protected Expression readTypeInfo(Class<?> cls, Expression ignored, boolean inli
}
}

protected Expression readTypeInfoWithTarget(Class<?> cls) {
Reference classInfoHolderRef = addTypeInfoHolderField(cls);
return inlineInvoke(
typeResolverRef,
"readTypeInfo",
classInfoTypeRef,
readContextRef,
getClassExpr(cls),
classInfoHolderRef);
}

protected TypeRef<?> getSerializerType(TypeRef<?> objType) {
return getSerializerType(objType.getRawType());
}
Expand Down Expand Up @@ -1334,11 +1390,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);
Expand Down Expand Up @@ -1398,9 +1461,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(
Expand All @@ -1415,6 +1497,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);
}
Expand Down Expand Up @@ -2344,6 +2429,14 @@ private Expression deserializeForNotNullForField(
return StringSerializer.readStringExpr(
getOrCreateStringSerializer(), buffer, config.compressString());
}
if (isEnumType(cls)) {
Expression enumSerializer =
cast(
serializer == null ? getSerializerForField(cls) : serializer,
TypeRef.of(EnumSerializer.class));
return new Invoke(
enumSerializer, "readValue", TypeRef.of(Enum.class), readContextRef(), buffer);
}
Expression obj;
if (usesPrimitiveListArrayProtocol(descriptor)) {
serializer = getPrimitiveListArraySerializer(cls);
Expand Down Expand Up @@ -2588,14 +2681,20 @@ protected Expression readForNotNullNonFinal(
|| typeInfo.getTypeId() == Types.NAMED_COMPATIBLE_STRUCT)) {
String name = ctx.newName(StringUtils.uncapitalize(rawType.getSimpleName()) + "Class");
Expression clsExpr = staticClassFieldExpr(rawType, name);
Reference classInfoHolderRef = addTypeInfoHolderField(rawType);
classInfo =
inlineInvoke(
typeResolverRef, "readTypeInfo", classInfoTypeRef, readContextRef, clsExpr);
typeResolverRef,
"readTypeInfo",
classInfoTypeRef,
readContextRef,
clsExpr,
classInfoHolderRef);
} else {
classInfo = readTypeInfo(getRawType(typeRef), buffer);
}
} else {
classInfo = readTypeInfo(getRawType(typeRef), buffer);
classInfo = readTypeInfo(rawType, buffer);
}
serializer = inlineInvoke(classInfo, "getSerializer", SERIALIZER_TYPE);
}
Expand Down Expand Up @@ -2638,16 +2737,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());
Expand All @@ -2664,8 +2765,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);
Expand Down Expand Up @@ -2697,7 +2802,7 @@ protected Expression readCollectionCodegen(
Literal isDeclTypeFlag = ofInt(CollectionFlags.IS_DECL_ELEMENT_TYPE);
Expression isDeclType = eq(new BitAnd(flags, isDeclTypeFlag), isDeclTypeFlag);
Invoke serializer =
inlineInvoke(readTypeInfo(elemClass, buffer), "getSerializer", SERIALIZER_TYPE);
inlineInvoke(readTypeInfoWithTarget(elemClass), "getSerializer", SERIALIZER_TYPE);
TypeRef<?> serializerType = getSerializerType(elementType);
Expression elemSerializer; // make it in scope of `if(sameElementClass)`
boolean maybeDecl = typeResolver(r -> r.isSerializable(elemClass));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -537,6 +541,20 @@ public Object readRef() {
return refReader.getReadRef();
}

/** 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;
}

/** Variant of {@link #readRef()} that uses already resolved {@link TypeInfo}. */
public Object readRef(TypeInfo typeInfo) {
int nextReadRefId = refReader.tryPreserveRefId(buffer);
Expand Down
Loading
Loading