Skip to content
7 changes: 7 additions & 0 deletions .agents/languages/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ Load this file when changing anything under `java/` or when Java drives a cross-
by generated serializers, do extra self-review: inspect the generated output impact, preserve
unsafe/codegen optimizations unless intentionally changing them, and run validation appropriate to
the regression risk.
- For HotSpot generated-code boundaries, use cold type-subtree cost to select only generated child
or member graphs that need isolation, then route their hot receiver implementations through one
shared ordinary-class trampoline `invokeinterface` bytecode. The polymorphic call site prevents C2
from recursively inlining several large field graphs into one parent, while each receiver remains
an independent compilation unit whose internal scalar operations can still inline. Do not apply
the trampoline blanketly to handwritten codecs, root facades, or small generated subtrees, and do
not replace it with per-generated-class call sites without `PrintInlining` proof.
- Android and JVM serializers must use a unified wire protocol: each side must be able to
deserialize data written by the other side. If implementation paths diverge, the writer must emit
enough metadata for either reader to identify and parse that path correctly; add both
Expand Down
5 changes: 5 additions & 0 deletions .agents/skills/fory-performance-optimization/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Deliver measurable performance improvements in Apache Fory without protocol drif
- Keep only measured wins or explicitly requested architecture cleanups.
- Revert speculative changes that do not pay off.
- Align with reference runtimes (usually C++ first, then Rust/Java) when behavior and ownership models differ.
- When containing HotSpot-generated code, select boundaries from cold type-subtree cost and route
only the large generated receiver implementations through one shared trampoline `invokeinterface`
bytecode. Do not blanket handwritten codecs, root facades, or small generated subtrees. Verify
with `PrintInlining` that C2 retains this outer polymorphic boundary while each independently
compiled receiver still inlines its internal scalar subtree.

## Enforce Hard Constraints

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,18 @@ private ObjectCollectionCodec(CollectionFactory factory, JsonTypeInfo elementTyp
this.elementTypeInfo = elementTypeInfo;
}

/**
* Returns the resolved element binding for generated parent codecs that own a schema-local
* collection loop.
*
* <p>The binding remains live because generated element capabilities may replace the initial
* object codec after this collection codec is constructed.
*/
@Internal
public JsonTypeInfo elementTypeInfo() {
return elementTypeInfo;
}

@Override
public void writeString(StringJsonWriter writer, Collection<?> value) {
if (value == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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.
*/

package org.apache.fory.json.codec;

import org.apache.fory.annotation.Internal;
import org.apache.fory.json.reader.Utf8JsonReader;
import org.apache.fory.json.writer.StringJsonWriter;
import org.apache.fory.json.writer.Utf8JsonWriter;

/**
* Shared interface invocation sites emitted by generated JSON codecs.
*
* <p>Generated codecs call these ordinary-class methods instead of emitting an interface-call
* bytecode at each generated caller. Multiple hot generated receiver implementations therefore
* share one call-site profile. On HotSpot, that polymorphic profile keeps C2 from recursively
* inlining several large generated field graphs into one parent compilation. Each receiver is still
* compiled as an independent unit, where its scalar writer calls remain eligible for inlining.
*
* <p>The code generator selects these boundaries from resolved type-subtree cost. These interfaces
* are a codegen implementation detail: handwritten codecs, containers, readers, and writers must
* keep their owned calls direct so unrelated workloads do not share this type profile. Generated
* scalar or small-subtree calls also remain direct.
*
* <p>Keep each method as a direct interface invocation. Specializing a method for one generated
* codec or moving the invocation into generated source makes its call site monomorphic and can
* restore the recursive inlining this boundary is intended to contain.
*/
@Internal
public final class JsonTrampolineInvoke {
/** A generated-codec-owned UTF-8 write group bound to that codec instance. */
@Internal
public interface Utf8WriteGroup {
void write(Utf8JsonWriter writer, Object value);
}

/** A generated-codec-owned String write group bound to that codec instance. */
@Internal
public interface StringWriteGroup {
void write(StringJsonWriter writer, Object value);
}

/** A generated-codec-owned UTF-8 read group bound to that codec instance. */
@Internal
public interface Utf8ReadGroup {
boolean read(Utf8JsonReader reader, Object value, long[] fieldHashes);
}

/** A generated-codec-owned complex UTF-8 field tree bound to that codec instance. */
@Internal
public interface Utf8ReadField {
void read(Utf8JsonReader reader, Object value);
}

private JsonTrampolineInvoke() {}

/** Invokes a UTF-8 group through the shared generated-code call site. */
public static void writeUtf8Group(Utf8WriteGroup group, Utf8JsonWriter writer, Object value) {
group.write(writer, value);
}

/** Invokes a String group through the shared generated-code call site. */
public static void writeStringGroup(
StringWriteGroup group, StringJsonWriter writer, Object value) {
group.write(writer, value);
}

/** Invokes a UTF-8 read group through the shared generated-code call site. */
public static boolean readUtf8Group(
Utf8ReadGroup group, Utf8JsonReader reader, Object value, long[] fieldHashes) {
return group.read(reader, value, fieldHashes);
}

/** Invokes a complex UTF-8 field tree through the shared generated-code call site. */
public static void readUtf8Field(Utf8ReadField field, Utf8JsonReader reader, Object value) {
field.read(reader, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private Class<?> buildStringWriter(ObjectCodec<?> codec) {
AnyInfo any = codec.anyInfo();
String code =
any == null || any.writeField() == null && any.writeGetter() == null
? new StringWriterCodegen(this).genWriterCode(builder, type, codec.writeFields())
? new StringWriterCodegen(this).genWriterCode(builder, type, codec, codec.writeFields())
: new StringWriterCodegen(this)
.genAnyWriterCode(builder, type, codec.writeFields(), any);
return compileCodecClass(generatedPackage, className, code);
Expand All @@ -206,7 +206,7 @@ private Class<?> buildUtf8Writer(ObjectCodec<?> codec) {
AnyInfo any = codec.anyInfo();
String code =
any == null || any.writeField() == null && any.writeGetter() == null
? new Utf8WriterCodegen(this).genWriterCode(builder, type, codec.writeFields())
? new Utf8WriterCodegen(this).genWriterCode(builder, type, codec, codec.writeFields())
: new Utf8WriterCodegen(this).genAnyWriterCode(builder, type, codec.writeFields(), any);
return compileCodecClass(generatedPackage, className, code);
}
Expand All @@ -227,7 +227,7 @@ private Class<?> buildLatin1Reader(ObjectCodec<?> codec) {
String code =
any == null || any.readField() == null && any.readSetter() == null
? new Latin1ReaderCodegen(this)
.genReaderCode(builder, type, codec.readFields(), codec.creatorInfo())
.genReaderCode(builder, type, codec, codec.readFields(), codec.creatorInfo())
: new Latin1ReaderCodegen(this)
.genAnyReaderCode(builder, type, codec.readFields(), codec.creatorInfo(), any);
return compileCodecClass(generatedPackage, className, code);
Expand All @@ -249,7 +249,7 @@ private Class<?> buildUtf16Reader(ObjectCodec<?> codec) {
String code =
any == null || any.readField() == null && any.readSetter() == null
? new Utf16ReaderCodegen(this)
.genReaderCode(builder, type, codec.readFields(), codec.creatorInfo())
.genReaderCode(builder, type, codec, codec.readFields(), codec.creatorInfo())
: new Utf16ReaderCodegen(this)
.genAnyReaderCode(builder, type, codec.readFields(), codec.creatorInfo(), any);
return compileCodecClass(generatedPackage, className, code);
Expand All @@ -271,7 +271,7 @@ private Class<?> buildUtf8Reader(ObjectCodec<?> codec) {
String code =
any == null || any.readField() == null && any.readSetter() == null
? new Utf8ReaderCodegen(this)
.genReaderCode(builder, type, codec.readFields(), codec.creatorInfo())
.genReaderCode(builder, type, codec, codec.readFields(), codec.creatorInfo())
: new Utf8ReaderCodegen(this)
.genAnyReaderCode(builder, type, codec.readFields(), codec.creatorInfo(), any);
return compileCodecClass(generatedPackage, className, code);
Expand Down
Loading
Loading