From 493851a0db2e219f3ae2523018acbd8f59a69718 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:37:36 +0300 Subject: [PATCH 01/10] Use /// docs for Concrete annotation --- .../com/codename1/annotations/Concrete.java | 43 +++++++++++++++ .../impl/CodenameOneImplementation.java | 2 + docs/developer-guide/performance.asciidoc | 22 ++++++++ .../tools/translator/ByteCodeClass.java | 9 ++++ .../codename1/tools/translator/Parser.java | 12 +++++ .../translator/bytecodes/CustomInvoke.java | 52 ++++++++++++++++--- .../tools/translator/bytecodes/Invoke.java | 26 ++++++++-- 7 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 CodenameOne/src/com/codename1/annotations/Concrete.java diff --git a/CodenameOne/src/com/codename1/annotations/Concrete.java b/CodenameOne/src/com/codename1/annotations/Concrete.java new file mode 100644 index 0000000000..c1e5765d7e --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/Concrete.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// Indicates that a class has a known concrete implementation that ParparVM can +/// target directly in native (C/Objective-C) pipelines. +/// +/// When present, the translator may bypass virtual lookup when invoking methods +/// on this type by preferring the concrete class provided in {@link #name()}, +/// and falling back to the annotated type implementation if the concrete class +/// doesn't implement the method. +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface Concrete { + /// The fully-qualified class name of the concrete implementation to prefer + /// during ParparVM native translation. + String name(); +} diff --git a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java index 291b7750d0..4777cde002 100644 --- a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java +++ b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java @@ -23,6 +23,7 @@ */ package com.codename1.impl; +import com.codename1.annotations.Concrete; import com.codename1.capture.VideoCaptureConstraints; import com.codename1.codescan.CodeScanner; import com.codename1.components.AudioRecorderComponent; @@ -109,6 +110,7 @@ /// Display specifically for key, pointer events and screen resolution. /// /// @author Shai Almog +@Concrete(name = "com.codename1.impl.ios.IOSImplementation") public abstract class CodenameOneImplementation { /// Indicates the range of "hard" RTL bidi characters in unicode private static final int RTL_RANGE_BEGIN = 0x590; diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index 5697a64eb8..ead89dfd64 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -61,6 +61,28 @@ For ParparVM-generated native code, we now support method-level optimization hin TIP: Use these on methods that are both performance-critical and well-covered by tests. These annotations intentionally trade runtime safety diagnostics for speed. +===== Class-level concrete implementation hints + +For native ParparVM output (C/Objective-C), you can also provide a class-level hint that a base type always maps to a known concrete subclass at runtime: + +* `@Concrete(name="fully.qualified.ConcreteClassName")` + + Allows the translator to bypass virtual table lookup for `invokevirtual` calls on the annotated base class. + The translator first attempts a direct call on the concrete class; if the method is not implemented there, it falls back to the annotated base class implementation. + +This is useful for platform abstraction classes where one implementation is guaranteed in the native pipeline. + +For example, Codename One annotates: + +[source,java] +---- +@Concrete(name = "com.codename1.impl.ios.IOSImplementation") +public abstract class CodenameOneImplementation { + // ... +} +---- + +NOTE: This hint is intended for ParparVM native translation and does not apply to the JavaScript backend. + ===== Fast method-stack path The translator can emit a fast method-stack prologue/epilogue (`DEFINE_METHOD_STACK_FAST_*` and `CN1_FAST_RETURN_RELEASE`) for methods that meet strict safety criteria. diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java index 46fbebdad6..5a710c4994 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java @@ -66,6 +66,7 @@ public void setIsAnnotation(boolean isAnnotation) { private String clsName; private String originalClassName; private String baseClass; + private String concreteClass; private List baseInterfaces; private boolean isInterface; private boolean isAbstract; @@ -1891,6 +1892,14 @@ public String getBaseClass() { return baseClass; } + public String getConcreteClass() { + return concreteClass; + } + + public void setConcreteClass(String concreteClass) { + this.concreteClass = concreteClass; + } + public void setSourceFile(String sourceFile) { this.sourceFile = sourceFile; } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java index 2b888883d9..f176e37c6a 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java @@ -51,6 +51,7 @@ public class Parser extends ClassVisitor { private static final String DISABLE_DEBUG_INFO_ANNOTATION = "Lcom/codename1/annotations/DisableDebugInfo;"; private static final String DISABLE_NULL_AND_ARRAY_BOUNDS_CHECKS_ANNOTATION = "Lcom/codename1/annotations/DisableNullChecksAndArrayBoundsChecks;"; + private static final String CONCRETE_ANNOTATION = "Lcom/codename1/annotations/Concrete;"; private ByteCodeClass cls; private String clsName; private static String[] nativeSources; @@ -699,6 +700,17 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (CONCRETE_ANNOTATION.equals(desc)) { + return new AnnotationVisitorWrapper(super.visitAnnotation(desc, visible)) { + @Override + public void visit(String name, Object value) { + if ("name".equals(name) && value instanceof String) { + cls.setConcreteClass(((String)value).replace('.', '/')); + } + super.visit(name, value); + } + }; + } return new AnnotationVisitorWrapper(super.visitAnnotation(desc, visible)); } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index 7abd68daee..3942889e0e 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -23,6 +23,7 @@ package com.codename1.tools.translator.bytecodes; import com.codename1.tools.translator.ByteCodeClass; +import com.codename1.tools.translator.ByteCodeTranslator; import com.codename1.tools.translator.ByteCodeMethodArg; import com.codename1.tools.translator.BytecodeMethod; import com.codename1.tools.translator.Parser; @@ -134,6 +135,26 @@ private String findActualOwner(ByteCodeClass bc) { } return findActualOwner(bc.getBaseClassObject()); } + + private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { + if (ownerClass == null || ownerClass.getConcreteClass() == null) { + return null; + } + ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); + if (concreteClass == null) { + System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); + return null; + } + ByteCodeClass concreteOwner = concreteClass.findMethodOwner(name, desc); + if (concreteOwner != null) { + return concreteOwner.getClsName(); + } + ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); + if (fallbackOwner != null) { + return fallbackOwner.getClsName(); + } + return null; + } public boolean methodHasReturnValue() { return BytecodeMethod.appendMethodSignatureSuffixFromDesc(desc, new StringBuilder(), new ArrayList<>()) != null; @@ -160,6 +181,7 @@ public boolean appendExpression(StringBuilder b) { owner = Util.resolveInvokeSpecialOwner(owner, name, desc); } + String invokeOwner = owner; StringBuilder bld = new StringBuilder(); boolean isVirtualCall = false; if(origOpcode == Opcodes.INVOKEINTERFACE || origOpcode == Opcodes.INVOKEVIRTUAL) { @@ -176,6 +198,12 @@ public boolean appendExpression(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; + } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + invokeOwner = resolvedConcreteOwner; + isVirtual = false; + } } } @@ -191,12 +219,12 @@ public boolean appendExpression(StringBuilder b) { if(origOpcode == Opcodes.INVOKESTATIC) { // find the actual class of the static method to work around javac not defining it correctly ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); - owner = findActualOwner(bc); + invokeOwner = findActualOwner(bc); } - if (owner.startsWith("[")) { + if (invokeOwner.startsWith("[")) { bld.append("java_lang_Object"); } else{ - bld.append(owner.replace('/', '_').replace('$', '_')); + bld.append(invokeOwner.replace('/', '_').replace('$', '_')); } bld.append("_"); if(name.equals("")) { @@ -264,7 +292,9 @@ public void appendInstruction(StringBuilder b) { owner = Util.resolveInvokeSpecialOwner(owner, name, desc); } + String invokeOwner = owner; StringBuilder bld = new StringBuilder(); + boolean isVirtualCall = false; if(origOpcode == Opcodes.INVOKEINTERFACE || origOpcode == Opcodes.INVOKEVIRTUAL) { b.append(" "); @@ -279,12 +309,19 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; + } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + invokeOwner = resolvedConcreteOwner; + isVirtual = false; + } } } } if (isVirtual) { bld.append("virtual_"); + isVirtualCall = true; } } else { b.append(" "); @@ -293,12 +330,12 @@ public void appendInstruction(StringBuilder b) { if(origOpcode == Opcodes.INVOKESTATIC) { // find the actual class of the static method to work around javac not defining it correctly ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); - owner = findActualOwner(bc); + invokeOwner = findActualOwner(bc); } - if (owner.startsWith("[")) { + if (invokeOwner.startsWith("[")) { bld.append("java_lang_Object"); } else{ - bld.append(owner.replace('/', '_').replace('$', '_')); + bld.append(invokeOwner.replace('/', '_').replace('$', '_')); } bld.append("_"); if(name.equals("")) { @@ -313,6 +350,9 @@ public void appendInstruction(StringBuilder b) { bld.append("__"); ArrayList args = new ArrayList<>(); String returnVal = BytecodeMethod.appendMethodSignatureSuffixFromDesc(desc, bld, args); + if (isVirtualCall) { + BytecodeMethod.addVirtualMethodsInvoked(bld.substring("virtual_".length())); + } int numLiteralArgs = this.getNumLiteralArgs(); if (numLiteralArgs > 0) { b.append("/* CustomInvoke */"); diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 5e10c18876..d214709cbf 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -24,6 +24,7 @@ package com.codename1.tools.translator.bytecodes; import com.codename1.tools.translator.ByteCodeClass; +import com.codename1.tools.translator.ByteCodeTranslator; import com.codename1.tools.translator.ByteCodeMethodArg; import com.codename1.tools.translator.BytecodeMethod; import com.codename1.tools.translator.Parser; @@ -144,6 +145,7 @@ public void appendInstruction(StringBuilder b) { owner = Util.resolveInvokeSpecialOwner(owner, name, desc); } + String invokeOwner = owner; StringBuilder bld = new StringBuilder(); boolean isVirtualCall = false; if(opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEVIRTUAL) { @@ -161,6 +163,24 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; + } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT + && bc.getConcreteClass() != null) { + ByteCodeClass concreteClass = Parser.getClassObject(bc.getConcreteClass().replace('/', '_').replace('$', '_')); + if (concreteClass != null) { + ByteCodeClass concreteOwner = concreteClass.findMethodOwner(name, desc); + if (concreteOwner != null) { + invokeOwner = concreteOwner.getClsName(); + isVirtual = false; + } else { + ByteCodeClass fallbackOwner = bc.findMethodOwner(name, desc); + if (fallbackOwner != null) { + invokeOwner = fallbackOwner.getClsName(); + isVirtual = false; + } + } + } else { + System.err.println("WARNING: Failed to find concrete class object for "+bc.getClsName()+": "+bc.getConcreteClass()); + } } } } @@ -175,14 +195,14 @@ public void appendInstruction(StringBuilder b) { if(opcode == Opcodes.INVOKESTATIC) { // find the actual class of the static method to work around javac not defining it correctly ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); - owner = findActualOwner(bc); + invokeOwner = findActualOwner(bc); } - if (owner.startsWith("[")) { + if (invokeOwner.startsWith("[")) { // Kotlin seems to generate calls to toString() on arrays using the array class // as an owner. We'll just change this to java_lang_Object instead. bld.append("java_lang_Object"); } else{ - bld.append(owner.replace('/', '_').replace('$', '_')); + bld.append(invokeOwner.replace('/', '_').replace('$', '_')); } bld.append("_"); if(name.equals("")) { From d73399caba849fe51ef9d4cd986dc79cc88e6e6b Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:59:24 +0300 Subject: [PATCH 02/10] Fix concrete invoke dependencies for generated headers --- .../translator/bytecodes/CustomInvoke.java | 12 ++++- .../tools/translator/bytecodes/Invoke.java | 54 ++++++++++++------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index 3942889e0e..0c9e9145a8 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -92,7 +92,15 @@ public String getMethodUsed() { @Override public void addDependencies(List dependencyList) { - String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); + String dependencyOwner = owner; + if (origOpcode == Opcodes.INVOKEVIRTUAL) { + ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + dependencyOwner = resolvedConcreteOwner; + } + } + String t = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); @@ -102,7 +110,7 @@ public void addDependencies(List dependencyList) { if(origOpcode != Opcodes.INVOKEINTERFACE && origOpcode != Opcodes.INVOKEVIRTUAL) { return; } - bld.append(owner.replace('/', '_').replace('$', '_')); + bld.append(dependencyOwner.replace('/', '_').replace('$', '_')); bld.append("_"); if(name.equals("")) { bld.append("__INIT__"); diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index d214709cbf..30a630da22 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -90,7 +90,15 @@ private String getCMethodName() { @Override public void addDependencies(List dependencyList) { - String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); + String dependencyOwner = owner; + if (opcode == Opcodes.INVOKEVIRTUAL) { + ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + dependencyOwner = resolvedConcreteOwner; + } + } + String t = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); @@ -100,7 +108,7 @@ public void addDependencies(List dependencyList) { if(opcode != Opcodes.INVOKEINTERFACE && opcode != Opcodes.INVOKEVIRTUAL) { return; } - bld.append(owner.replace('/', '_').replace('$', '_')); + bld.append(dependencyOwner.replace('/', '_').replace('$', '_')); bld.append("_"); if(name.equals("")) { bld.append("__INIT__"); @@ -133,6 +141,26 @@ private String findActualOwner(ByteCodeClass bc) { } return findActualOwner(bc.getBaseClassObject()); } + + private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { + if (ownerClass == null || ownerClass.getConcreteClass() == null) { + return null; + } + ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); + if (concreteClass == null) { + System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); + return null; + } + ByteCodeClass concreteOwner = concreteClass.findMethodOwner(name, desc); + if (concreteOwner != null) { + return concreteOwner.getClsName(); + } + ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); + if (fallbackOwner != null) { + return fallbackOwner.getClsName(); + } + return null; + } @Override public void appendInstruction(StringBuilder b) { @@ -163,23 +191,11 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; - } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT - && bc.getConcreteClass() != null) { - ByteCodeClass concreteClass = Parser.getClassObject(bc.getConcreteClass().replace('/', '_').replace('$', '_')); - if (concreteClass != null) { - ByteCodeClass concreteOwner = concreteClass.findMethodOwner(name, desc); - if (concreteOwner != null) { - invokeOwner = concreteOwner.getClsName(); - isVirtual = false; - } else { - ByteCodeClass fallbackOwner = bc.findMethodOwner(name, desc); - if (fallbackOwner != null) { - invokeOwner = fallbackOwner.getClsName(); - isVirtual = false; - } - } - } else { - System.err.println("WARNING: Failed to find concrete class object for "+bc.getClsName()+": "+bc.getConcreteClass()); + } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + invokeOwner = resolvedConcreteOwner; + isVirtual = false; } } } From a39b12b975e5ced7d2e168ce4a02f3fb0f9d459b Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:25:44 +0300 Subject: [PATCH 03/10] Only devirtualize to concrete class for declared methods --- .../src/com/codename1/tools/translator/ByteCodeClass.java | 5 +++++ .../codename1/tools/translator/bytecodes/CustomInvoke.java | 5 ++--- .../src/com/codename1/tools/translator/bytecodes/Invoke.java | 5 ++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java index 5a710c4994..6932bd77f2 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java @@ -343,6 +343,11 @@ private BytecodeMethod findDeclaredMethod(String name, String desc) { return null; } + public boolean hasDeclaredNonAbstractMethod(String name, String desc) { + BytecodeMethod declaredMethod = findDeclaredMethod(name, desc); + return declaredMethod != null && !declaredMethod.isAbstract(); + } + public void unmark() { marked = false; } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index 0c9e9145a8..ec0f96e7e2 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -153,9 +153,8 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); return null; } - ByteCodeClass concreteOwner = concreteClass.findMethodOwner(name, desc); - if (concreteOwner != null) { - return concreteOwner.getClsName(); + if (concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { + return concreteClass.getClsName(); } ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); if (fallbackOwner != null) { diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 30a630da22..665b87c078 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -151,9 +151,8 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); return null; } - ByteCodeClass concreteOwner = concreteClass.findMethodOwner(name, desc); - if (concreteOwner != null) { - return concreteOwner.getClsName(); + if (concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { + return concreteClass.getClsName(); } ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); if (fallbackOwner != null) { From c5cd7169052faa67357ea96c61c0509d991e45db Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 10:55:09 +0300 Subject: [PATCH 04/10] Skip concrete dispatch inside annotated base class internals --- .../tools/translator/bytecodes/CustomInvoke.java | 8 ++++++++ .../com/codename1/tools/translator/bytecodes/Invoke.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index ec0f96e7e2..4fdda39c5e 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -148,6 +148,14 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { if (ownerClass == null || ownerClass.getConcreteClass() == null) { return null; } + if (getMethod() != null) { + String currentClass = getMethod().getClsName(); + String ownerName = ownerClass.getClsName(); + if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { + ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); + return fallbackOwner != null ? fallbackOwner.getClsName() : null; + } + } ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); if (concreteClass == null) { System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 665b87c078..6784edc830 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -146,6 +146,14 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { if (ownerClass == null || ownerClass.getConcreteClass() == null) { return null; } + if (getMethod() != null) { + String currentClass = getMethod().getClsName(); + String ownerName = ownerClass.getClsName(); + if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { + ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); + return fallbackOwner != null ? fallbackOwner.getClsName() : null; + } + } ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); if (concreteClass == null) { System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); From 9dd005ebec5cb1b540577aedb3a550c1093a22b7 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 11:48:07 +0300 Subject: [PATCH 05/10] Fallback to base owner when invoke context is unavailable --- .../codename1/tools/translator/bytecodes/CustomInvoke.java | 6 ++++-- .../com/codename1/tools/translator/bytecodes/Invoke.java | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index 4fdda39c5e..e2045e2ae1 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -148,11 +148,14 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { if (ownerClass == null || ownerClass.getConcreteClass() == null) { return null; } + ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); + if (getMethod() == null) { + return fallbackOwner != null ? fallbackOwner.getClsName() : null; + } if (getMethod() != null) { String currentClass = getMethod().getClsName(); String ownerName = ownerClass.getClsName(); if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { - ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); return fallbackOwner != null ? fallbackOwner.getClsName() : null; } } @@ -164,7 +167,6 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { if (concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { return concreteClass.getClsName(); } - ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); if (fallbackOwner != null) { return fallbackOwner.getClsName(); } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 6784edc830..b1f48d276a 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -146,11 +146,14 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { if (ownerClass == null || ownerClass.getConcreteClass() == null) { return null; } + ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); + if (getMethod() == null) { + return fallbackOwner != null ? fallbackOwner.getClsName() : null; + } if (getMethod() != null) { String currentClass = getMethod().getClsName(); String ownerName = ownerClass.getClsName(); if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { - ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); return fallbackOwner != null ? fallbackOwner.getClsName() : null; } } @@ -162,7 +165,6 @@ private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { if (concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { return concreteClass.getClsName(); } - ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); if (fallbackOwner != null) { return fallbackOwner.getClsName(); } From 321fad1abd1e856477d80085661a6d7ba9123c14 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:09:40 +0300 Subject: [PATCH 06/10] Set instruction method context before dependency resolution --- .../src/com/codename1/tools/translator/BytecodeMethod.java | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index d05501af9c..c1b6413988 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -1473,6 +1473,7 @@ public void setMaxes(int maxStack, int maxLocals) { private void addInstruction(Instruction i) { instructions.add(i); + i.setMethod(this); i.addDependencies(dependentClasses); if (dependencyGraph != null) { String methodUsed = i.getMethodUsed(); From 802d37e758362493f0e392c9afc10d91f037b102 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 12:57:30 +0300 Subject: [PATCH 07/10] Preserve base virtual dependencies with concrete call targets --- .../tools/translator/bytecodes/CustomInvoke.java | 11 +++++++++-- .../codename1/tools/translator/bytecodes/Invoke.java | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index e2045e2ae1..f2635179a7 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -100,17 +100,24 @@ public void addDependencies(List dependencyList) { dependencyOwner = resolvedConcreteOwner; } } - String t = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); + String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); } + if (!owner.equals(dependencyOwner)) { + String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); + concreteDependency = unarray(concreteDependency); + if (concreteDependency != null && !dependencyList.contains(concreteDependency)) { + dependencyList.add(concreteDependency); + } + } StringBuilder bld = new StringBuilder(); if(origOpcode != Opcodes.INVOKEINTERFACE && origOpcode != Opcodes.INVOKEVIRTUAL) { return; } - bld.append(dependencyOwner.replace('/', '_').replace('$', '_')); + bld.append(owner.replace('/', '_').replace('$', '_')); bld.append("_"); if(name.equals("")) { bld.append("__INIT__"); diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index b1f48d276a..5b31303f61 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -98,17 +98,24 @@ public void addDependencies(List dependencyList) { dependencyOwner = resolvedConcreteOwner; } } - String t = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); + String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); } + if (!owner.equals(dependencyOwner)) { + String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); + concreteDependency = unarray(concreteDependency); + if (concreteDependency != null && !dependencyList.contains(concreteDependency)) { + dependencyList.add(concreteDependency); + } + } StringBuilder bld = new StringBuilder(); if(opcode != Opcodes.INVOKEINTERFACE && opcode != Opcodes.INVOKEVIRTUAL) { return; } - bld.append(dependencyOwner.replace('/', '_').replace('$', '_')); + bld.append(owner.replace('/', '_').replace('$', '_')); bld.append("_"); if(name.equals("")) { bld.append("__INIT__"); From 1df80c66c6d7c5e16f2e307f82c56450fe4feb71 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:04:14 +0300 Subject: [PATCH 08/10] Gate concrete invoke devirtualization behind opt-in flag --- docs/developer-guide/performance.asciidoc | 1 + .../tools/translator/bytecodes/CustomInvoke.java | 10 +++++++--- .../codename1/tools/translator/bytecodes/Invoke.java | 7 +++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index ead89dfd64..8054b69b1e 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -82,6 +82,7 @@ public abstract class CodenameOneImplementation { ---- NOTE: This hint is intended for ParparVM native translation and does not apply to the JavaScript backend. +NOTE: Concrete invoke devirtualization is currently guarded by the `cn1.enableConcreteInvokeOptimization` system property in the translator so it can be enabled explicitly during staged rollout. ===== Fast method-stack path diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index f2635179a7..6d314080f3 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -38,6 +38,8 @@ * @author shannah */ public class CustomInvoke extends Instruction { + private static final boolean ENABLE_CONCRETE_INVOKE_OPTIMIZATION = + Boolean.getBoolean("cn1.enableConcreteInvokeOptimization"); private String owner; private final String name; private final String desc; @@ -93,7 +95,7 @@ public String getMethodUsed() { @Override public void addDependencies(List dependencyList) { String dependencyOwner = owner; - if (origOpcode == Opcodes.INVOKEVIRTUAL) { + if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION && origOpcode == Opcodes.INVOKEVIRTUAL) { ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); if (resolvedConcreteOwner != null) { @@ -222,7 +224,8 @@ public boolean appendExpression(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; - } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { + } else if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION + && ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); if (resolvedConcreteOwner != null) { invokeOwner = resolvedConcreteOwner; @@ -333,7 +336,8 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; - } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { + } else if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION + && ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); if (resolvedConcreteOwner != null) { invokeOwner = resolvedConcreteOwner; diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 5b31303f61..4e2599def4 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -39,6 +39,8 @@ * @author Shai Almog */ public class Invoke extends Instruction { + private static final boolean ENABLE_CONCRETE_INVOKE_OPTIMIZATION = + Boolean.getBoolean("cn1.enableConcreteInvokeOptimization"); private String owner; private final String name; private final String desc; @@ -91,7 +93,7 @@ private String getCMethodName() { @Override public void addDependencies(List dependencyList) { String dependencyOwner = owner; - if (opcode == Opcodes.INVOKEVIRTUAL) { + if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION && opcode == Opcodes.INVOKEVIRTUAL) { ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); if (resolvedConcreteOwner != null) { @@ -207,7 +209,8 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; - } else if (ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { + } else if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION + && ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); if (resolvedConcreteOwner != null) { invokeOwner = resolvedConcreteOwner; From 8f6a2ba122ce6aa356be2b65cdacd4ae0643056d Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:04:20 +0300 Subject: [PATCH 09/10] Revert concrete invoke rewriting to restore stable dispatch --- docs/developer-guide/performance.asciidoc | 1 - .../tools/translator/ByteCodeClass.java | 5 -- .../translator/bytecodes/CustomInvoke.java | 61 ------------------- .../tools/translator/bytecodes/Invoke.java | 54 ---------------- 4 files changed, 121 deletions(-) diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index 8054b69b1e..ead89dfd64 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -82,7 +82,6 @@ public abstract class CodenameOneImplementation { ---- NOTE: This hint is intended for ParparVM native translation and does not apply to the JavaScript backend. -NOTE: Concrete invoke devirtualization is currently guarded by the `cn1.enableConcreteInvokeOptimization` system property in the translator so it can be enabled explicitly during staged rollout. ===== Fast method-stack path diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java index 6932bd77f2..5a710c4994 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java @@ -343,11 +343,6 @@ private BytecodeMethod findDeclaredMethod(String name, String desc) { return null; } - public boolean hasDeclaredNonAbstractMethod(String name, String desc) { - BytecodeMethod declaredMethod = findDeclaredMethod(name, desc); - return declaredMethod != null && !declaredMethod.isAbstract(); - } - public void unmark() { marked = false; } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index 6d314080f3..e4c920f3b5 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -23,7 +23,6 @@ package com.codename1.tools.translator.bytecodes; import com.codename1.tools.translator.ByteCodeClass; -import com.codename1.tools.translator.ByteCodeTranslator; import com.codename1.tools.translator.ByteCodeMethodArg; import com.codename1.tools.translator.BytecodeMethod; import com.codename1.tools.translator.Parser; @@ -38,8 +37,6 @@ * @author shannah */ public class CustomInvoke extends Instruction { - private static final boolean ENABLE_CONCRETE_INVOKE_OPTIMIZATION = - Boolean.getBoolean("cn1.enableConcreteInvokeOptimization"); private String owner; private final String name; private final String desc; @@ -94,26 +91,11 @@ public String getMethodUsed() { @Override public void addDependencies(List dependencyList) { - String dependencyOwner = owner; - if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION && origOpcode == Opcodes.INVOKEVIRTUAL) { - ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); - String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); - if (resolvedConcreteOwner != null) { - dependencyOwner = resolvedConcreteOwner; - } - } String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); } - if (!owner.equals(dependencyOwner)) { - String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); - concreteDependency = unarray(concreteDependency); - if (concreteDependency != null && !dependencyList.contains(concreteDependency)) { - dependencyList.add(concreteDependency); - } - } StringBuilder bld = new StringBuilder(); if(origOpcode != Opcodes.INVOKEINTERFACE && origOpcode != Opcodes.INVOKEVIRTUAL) { @@ -153,35 +135,6 @@ private String findActualOwner(ByteCodeClass bc) { return findActualOwner(bc.getBaseClassObject()); } - private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { - if (ownerClass == null || ownerClass.getConcreteClass() == null) { - return null; - } - ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); - if (getMethod() == null) { - return fallbackOwner != null ? fallbackOwner.getClsName() : null; - } - if (getMethod() != null) { - String currentClass = getMethod().getClsName(); - String ownerName = ownerClass.getClsName(); - if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { - return fallbackOwner != null ? fallbackOwner.getClsName() : null; - } - } - ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); - if (concreteClass == null) { - System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); - return null; - } - if (concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { - return concreteClass.getClsName(); - } - if (fallbackOwner != null) { - return fallbackOwner.getClsName(); - } - return null; - } - public boolean methodHasReturnValue() { return BytecodeMethod.appendMethodSignatureSuffixFromDesc(desc, new StringBuilder(), new ArrayList<>()) != null; } @@ -224,13 +177,6 @@ public boolean appendExpression(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; - } else if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION - && ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { - String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); - if (resolvedConcreteOwner != null) { - invokeOwner = resolvedConcreteOwner; - isVirtual = false; - } } } @@ -336,13 +282,6 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; - } else if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION - && ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { - String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); - if (resolvedConcreteOwner != null) { - invokeOwner = resolvedConcreteOwner; - isVirtual = false; - } } } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 4e2599def4..e196ca6129 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -24,7 +24,6 @@ package com.codename1.tools.translator.bytecodes; import com.codename1.tools.translator.ByteCodeClass; -import com.codename1.tools.translator.ByteCodeTranslator; import com.codename1.tools.translator.ByteCodeMethodArg; import com.codename1.tools.translator.BytecodeMethod; import com.codename1.tools.translator.Parser; @@ -39,8 +38,6 @@ * @author Shai Almog */ public class Invoke extends Instruction { - private static final boolean ENABLE_CONCRETE_INVOKE_OPTIMIZATION = - Boolean.getBoolean("cn1.enableConcreteInvokeOptimization"); private String owner; private final String name; private final String desc; @@ -92,26 +89,11 @@ private String getCMethodName() { @Override public void addDependencies(List dependencyList) { - String dependencyOwner = owner; - if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION && opcode == Opcodes.INVOKEVIRTUAL) { - ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); - String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); - if (resolvedConcreteOwner != null) { - dependencyOwner = resolvedConcreteOwner; - } - } String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); } - if (!owner.equals(dependencyOwner)) { - String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); - concreteDependency = unarray(concreteDependency); - if (concreteDependency != null && !dependencyList.contains(concreteDependency)) { - dependencyList.add(concreteDependency); - } - } StringBuilder bld = new StringBuilder(); if(opcode != Opcodes.INVOKEINTERFACE && opcode != Opcodes.INVOKEVIRTUAL) { @@ -151,35 +133,6 @@ private String findActualOwner(ByteCodeClass bc) { return findActualOwner(bc.getBaseClassObject()); } - private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { - if (ownerClass == null || ownerClass.getConcreteClass() == null) { - return null; - } - ByteCodeClass fallbackOwner = ownerClass.findMethodOwner(name, desc); - if (getMethod() == null) { - return fallbackOwner != null ? fallbackOwner.getClsName() : null; - } - if (getMethod() != null) { - String currentClass = getMethod().getClsName(); - String ownerName = ownerClass.getClsName(); - if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { - return fallbackOwner != null ? fallbackOwner.getClsName() : null; - } - } - ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); - if (concreteClass == null) { - System.err.println("WARNING: Failed to find concrete class object for " + ownerClass.getClsName() + ": " + ownerClass.getConcreteClass()); - return null; - } - if (concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { - return concreteClass.getClsName(); - } - if (fallbackOwner != null) { - return fallbackOwner.getClsName(); - } - return null; - } - @Override public void appendInstruction(StringBuilder b) { // special case for clone on an array which isn't a real method invocation @@ -209,13 +162,6 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; - } else if (ENABLE_CONCRETE_INVOKE_OPTIMIZATION - && ByteCodeTranslator.output != ByteCodeTranslator.OutputType.OUTPUT_TYPE_JAVASCRIPT) { - String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); - if (resolvedConcreteOwner != null) { - invokeOwner = resolvedConcreteOwner; - isVirtual = false; - } } } } From 49e47ea7eac7b498c60df939cc04c87ec97696e9 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 14 Apr 2026 14:04:25 +0300 Subject: [PATCH 10/10] Re-enable concrete invoke optimization with strict ownership checks --- .../tools/translator/ByteCodeClass.java | 5 +++ .../translator/bytecodes/CustomInvoke.java | 43 +++++++++++++++++++ .../tools/translator/bytecodes/Invoke.java | 37 ++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java index 5a710c4994..6932bd77f2 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java @@ -343,6 +343,11 @@ private BytecodeMethod findDeclaredMethod(String name, String desc) { return null; } + public boolean hasDeclaredNonAbstractMethod(String name, String desc) { + BytecodeMethod declaredMethod = findDeclaredMethod(name, desc); + return declaredMethod != null && !declaredMethod.isAbstract(); + } + public void unmark() { marked = false; } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index e4c920f3b5..2f3f74d296 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -91,11 +91,26 @@ public String getMethodUsed() { @Override public void addDependencies(List dependencyList) { + String dependencyOwner = owner; + if (origOpcode == Opcodes.INVOKEVIRTUAL) { + ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + dependencyOwner = resolvedConcreteOwner; + } + } String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); } + if (!owner.equals(dependencyOwner)) { + String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); + concreteDependency = unarray(concreteDependency); + if (concreteDependency != null && !dependencyList.contains(concreteDependency)) { + dependencyList.add(concreteDependency); + } + } StringBuilder bld = new StringBuilder(); if(origOpcode != Opcodes.INVOKEINTERFACE && origOpcode != Opcodes.INVOKEVIRTUAL) { @@ -135,6 +150,22 @@ private String findActualOwner(ByteCodeClass bc) { return findActualOwner(bc.getBaseClassObject()); } + private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { + if (ownerClass == null || ownerClass.getConcreteClass() == null || getMethod() == null) { + return null; + } + String currentClass = getMethod().getClsName(); + String ownerName = ownerClass.getClsName(); + if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { + return null; + } + ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); + if (concreteClass != null && concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { + return concreteClass.getClsName(); + } + return null; + } + public boolean methodHasReturnValue() { return BytecodeMethod.appendMethodSignatureSuffixFromDesc(desc, new StringBuilder(), new ArrayList<>()) != null; } @@ -177,6 +208,12 @@ public boolean appendExpression(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; + } else { + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + invokeOwner = resolvedConcreteOwner; + isVirtual = false; + } } } @@ -282,6 +319,12 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; + } else { + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + invokeOwner = resolvedConcreteOwner; + isVirtual = false; + } } } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index e196ca6129..d3d8dcf251 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -89,11 +89,26 @@ private String getCMethodName() { @Override public void addDependencies(List dependencyList) { + String dependencyOwner = owner; + if (opcode == Opcodes.INVOKEVIRTUAL) { + ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_')); + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + dependencyOwner = resolvedConcreteOwner; + } + } String t = owner.replace('.', '_').replace('/', '_').replace('$', '_'); t = unarray(t); if(t != null && !dependencyList.contains(t)) { dependencyList.add(t); } + if (!owner.equals(dependencyOwner)) { + String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_'); + concreteDependency = unarray(concreteDependency); + if (concreteDependency != null && !dependencyList.contains(concreteDependency)) { + dependencyList.add(concreteDependency); + } + } StringBuilder bld = new StringBuilder(); if(opcode != Opcodes.INVOKEINTERFACE && opcode != Opcodes.INVOKEVIRTUAL) { @@ -133,6 +148,22 @@ private String findActualOwner(ByteCodeClass bc) { return findActualOwner(bc.getBaseClassObject()); } + private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) { + if (ownerClass == null || ownerClass.getConcreteClass() == null || getMethod() == null) { + return null; + } + String currentClass = getMethod().getClsName(); + String ownerName = ownerClass.getClsName(); + if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) { + return null; + } + ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_')); + if (concreteClass != null && concreteClass.hasDeclaredNonAbstractMethod(name, desc)) { + return concreteClass.getClsName(); + } + return null; + } + @Override public void appendInstruction(StringBuilder b) { // special case for clone on an array which isn't a real method invocation @@ -162,6 +193,12 @@ public void appendInstruction(StringBuilder b) { } else { if (bc.isMethodPrivate(name, desc)) { isVirtual = false; + } else { + String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc); + if (resolvedConcreteOwner != null) { + invokeOwner = resolvedConcreteOwner; + isVirtual = false; + } } } }