refactor: port ASM super lookup to Java

This commit is contained in:
Algent committed 2026-09-03 11:05:16 +02:00
1 parent c0df2e0229
commit fa008b80bb
4 files changed
+80 -29

No files matched your search

+20
View File
@@ -1459,3 +1459,23 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec
and the clean jar repeats the API/disassembly/dump matches above. The forced Scala-compilation version guard is
retained; all five `@Mod` annotations match both packaged jar versions. External Scala-trait coverage remains green
and the existing manual client checks and Java-source bridge limitations remain outstanding.
- Added six Forge characterization cases for `ASMMixinCompiler.getSuper`, passing on untouched Scala and committed
separately as `c0df2e0`: owner/name filter short-circuiting, greedy Scala super-name stripping, exact inherited
signature selection and visibility, receiver recognition, argument indexing, failure paths, stack preservation,
virtual getter/callback order, returned option identity and descriptor rereads. Baseline: 326 JVM / 141 Forge.
The compiler singleton requires Forge initialization, so these behavior tests use its existing harness.
- Moved only `getSuper` into the existing Java `ClassInfoLookup`, retaining the public Scala entry point and
`Option.flatMap` callback dispatch. The current argument-count/stack-slot mismatch for wide arguments remains,
as do the lack of invocation-opcode and `This.owner` validation and the limited target-owner check. The caller's
`INVOKESPECIAL` guard, all trait rewrites and metadata/model behavior remain unchanged; algorithm fixes are separate.
- Saved reference source/jar, reports and 40 generated dumps in ignored `run/migration-super-reference/`. All 16 named
compiler APIs retain their names/descriptors/modifiers/generic signatures and private fields; 14 named classfiles
are byte-identical. All 218 other named compiler methods, 34 algorithm closures, 17 existing metadata-helper methods
and 143 other ASM/generator disassemblies match. All dump names/hashes match. One private Scala closure becomes a
Java callback, leaving 450 packaged classes and no new divergence entry. Sources total 209 Java files and 9 Scala
files / 1,675 nonblank Scala lines. Next: `ASMMixinCompiler.listSideOnly` annotation filtering, characterized first.
- Formatting/checkstyle/build and Forge pass, including clean verification after stopping Gradle: 326 JVM / 141 Forge,
zero failures/errors/skips. Characterization tests remain unchanged. The clean jar repeats the API/disassembly/dump
matches, the local dev config is unchanged, and all five `@Mod` versions match both packaged jar versions with the
forced Scala-compilation guard retained. External Scala-trait tests remain green; existing manual client checks and
Java-source model-bridge limitations remain outstanding.
+34 -10
View File
@@ -12,8 +12,8 @@ what breaks, and what is left. The other documents hold the reasoning.
| `JAVA_MIGRATION_MANUAL_CHECKS.md` | What no automated test can cover, and must be checked by hand in a client |
| `JAVA_MIGRATION_PROFILE.md` | The focused baseline, first measured result, findings, and exact rerun command |
Branch: `algent/java`. Base: `master`. 178 commits including the API-cleanup plan, divergence-log cleanup, ports
through the ASM descriptor/bridge helper extraction, and the ASM characterization backfill and review fixes.
Branch: `algent/java`. Base: `master`. 180 commits including the API-cleanup plan, divergence-log cleanup, ports
through ASM super-call lookup, and the ASM characterization backfill and review fixes.
## The one rule that matters
@@ -74,7 +74,7 @@ awk -F'"' '/<testsuite /{t+=$4;f+=$8;e+=$10} END{print "tests="t" failures="f" e
grep -o 'tests="[0-9]*" skipped="[0-9]*" failures="[0-9]*" errors="[0-9]*"' run/server/junit-out/TEST-*.xml
```
Current baseline: **326 plain-JVM tests and 135 Java 8 Forge dedicated-server tests passing.** The ignored local server
Current baseline: **326 plain-JVM tests and 141 Java 8 Forge dedicated-server tests passing.** The ignored local server
EULA is accepted in this checkout. GitHub Actions runs the same self-validating Forge suite in a dependent job after
the shared GTNH build; keep both jobs required.
@@ -280,7 +280,7 @@ across the port. It is also where the two shim constraints in the gotchas list w
`rayTraceAll`'s index production is now characterized, closing the gap the read-path cleanup left: the index written
into `ExtendedMOP.data` is what `reduceMOP` hands back to every click, activate, harvest and pick block.
209 Java files, 9 Scala files, 1,689 Scala lines left (non-blank; that is the metric this figure has always used).
209 Java files, 9 Scala files, 1,675 Scala lines left (non-blank; that is the metric this figure has always used).
## What is left, and in what order
@@ -704,12 +704,36 @@ match. The dev config and forced Scala-compilation version guard remain unchange
both packaged jar versions. External Scala-trait tests stay green. Existing manual client checks and the documented
Java-source limitations in the retained Scala model bridges are still outstanding.
**Next: `multipart/asm/ASMMixinCompiler.getSuper`, bounded to super-call recognition and lookup.** Characterize the
owner/name filters, Scala super-name pattern, stack receiver checks, argument indexing and inherited implementation
selection before extracting it. Use Forge for compiler initialization and preserve the current algorithm, including
any quirks exposed by tests. Keep composition/trait registration and rewriting separate, along with external Scala
trait support, retained metadata/stack models and `ScalaSignature` primitive/erased bridges. Save a fresh reference
and compare generated dumps before expanding scope.
`ASMMixinCompiler.getSuper` now delegates to the existing Java `ClassInfoLookup`. Six Forge characterization cases
passed on untouched Scala and were committed separately as `c0df2e0`. They cover filter short-circuiting and failures,
the greedy Scala super-name pattern, exact inherited signature selection, private/abstract exclusion, `Load(This)`
recognition, argument indexing, unchanged stack state, virtual getter/callback order, returned option identity and
descriptor rereads after callbacks. All tests pass unchanged after extraction. Forge is necessary for the original
compiler singleton's `LaunchClassLoader` initialization.
The existing algorithm intentionally remains: argument count is used as a stack-slot index, so a wide argument can
prevent super-call recognition; the helper does not validate the invocation opcode, target owner beyond its initial
same-owner exclusion, or `This.owner`. Its caller still applies the `INVOKESPECIAL` guard. These are characterized
existing behaviors, not new divergences or fixes. No trait rewrite or metadata/model algorithm changed.
Reference source/jar, reports, dumps and comparisons are under ignored `run/migration-super-reference/`. All 16 named
compiler APIs match, including generic signatures, modifiers and private fields; 14 named classfiles are byte-identical.
All 218 other named compiler methods, 34 algorithm closures, 17 existing metadata-helper methods and 143 other
ASM/generator disassemblies match. All 40 generated dump names/hashes match. A private Scala closure becomes a Java
callback, keeping 450 packaged classes; no new divergence entry is needed. Sources total 209 Java files and 9 Scala
files / 1,675 nonblank Scala lines.
Formatting/checkstyle/build and Forge pass after stopping Gradle and building clean: 326 JVM / 141 Forge, zero
failures/errors/skips. Tests are unchanged after the port; clean APIs and dumps match the reference. The dev config
and forced Scala-compilation guard remain unchanged, and all five `@Mod` annotations match both packaged jar versions.
External Scala-trait tests stay green; existing Java-source model-bridge limitations and manual client checks remain.
**Next: `multipart/asm/ASMMixinCompiler.listSideOnly`, bounded to annotation filtering.** Characterize annotation
selection, enum-side comparison, owner-name collection/deduplication and malformed/missing annotation values before
extracting it. Use Forge where side/singleton initialization requires it and preserve the current decoder behavior.
Keep composition/trait registration and rewriting separate, along with external Scala-trait support, retained
metadata/stack models and `ScalaSignature` primitive/erased bridges. Save a fresh reference and compare generated
dumps before expanding scope.
The remaining generated microblock traits still need the Phase 7 abstract-Java-mixin and side-only-member support
before their Java mixin inputs are safe; do not bypass those prerequisites by flattening their inheritance.
@@ -435,25 +435,7 @@ object ASMMixinCompiler {
def getSuper(
minsn: MethodInsnNode,
stack: StackAnalyser
): Option[MethodInfo] = {
import StackAnalyser._
if (minsn.owner == stack.owner.getInternalName)
return None // not a super call
// super calls are either to methods with the same name or contain a pattern 'target$$super$name' from the scala compiler
val methodName = stack.m.name.replaceAll(".+\\Q$$super$\\E", "")
if (minsn.name != methodName)
return None
stack.peek(Type.getType(minsn.desc).getArgumentTypes.length) match {
case Load(This(o)) =>
case _ => return None // have to be invoked on this
}
getClassInfo(stack.owner.getInternalName).superClass
.flatMap(_.findPublicImpl(methodName, minsn.desc))
}
): Option[MethodInfo] = ClassInfoLookup.getSuper(minsn, stack)
def getAndRegisterParentTraits(cnode: ClassNode) =
cnode.interfaces.map(getClassInfo).collect {
@@ -3,13 +3,18 @@ package codechicken.multipart.asm;
import java.lang.reflect.Method;
import java.util.Objects;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import codechicken.multipart.asm.ASMMixinCompiler.ClassInfo;
import codechicken.multipart.asm.ASMMixinCompiler.ClassInfo$;
import codechicken.multipart.asm.ASMMixinCompiler.MethodInfo;
import codechicken.multipart.asm.StackAnalyser.Load;
import codechicken.multipart.asm.StackAnalyser.StackEntry;
import codechicken.multipart.asm.StackAnalyser.This;
import scala.Array$;
import scala.Function1;
import scala.Function3;
@@ -173,4 +178,24 @@ final class ClassInfoLookup {
private static <R, E extends Throwable> R throwUnchecked(Throwable throwable) throws E {
throw (E) throwable;
}
static Option<MethodInfo> getSuper(final MethodInsnNode call, StackAnalyser stack) {
if (Objects.equals(call.owner, stack.owner().getInternalName())) return Option.empty();
final String methodName = stack.m().name.replaceAll(".+\\Q$$super$\\E", "");
if (!Objects.equals(call.name, methodName)) return Option.empty();
// Preserve the compiler's argument-count indexing, including its behavior for wide stack values.
StackEntry receiver = stack.peek(Type.getType(call.desc).getArgumentTypes().length);
if (!(receiver instanceof Load) || !(((Load) receiver).e() instanceof This)) return Option.empty();
return ASMMixinCompiler$.MODULE$.getClassInfo(stack.owner().getInternalName()).superClass()
.flatMap(new AbstractFunction1<ClassInfo, Option<MethodInfo>>() {
@Override
public Option<MethodInfo> apply(ClassInfo parent) {
return parent.findPublicImpl(methodName, call.desc);
}
});
}
}