Skip to content

Commit d9ae900

Browse files
authored
Add @concrete annotation and optimize ParparVM invoke resolution (#4746)
* Use /// docs for Concrete annotation * Fix concrete invoke dependencies for generated headers * Only devirtualize to concrete class for declared methods * Skip concrete dispatch inside annotated base class internals * Fallback to base owner when invoke context is unavailable * Set instruction method context before dependency resolution * Preserve base virtual dependencies with concrete call targets * Gate concrete invoke devirtualization behind opt-in flag * Revert concrete invoke rewriting to restore stable dispatch * Re-enable concrete invoke optimization with strict ownership checks
1 parent b818dba commit d9ae900

File tree

8 files changed

+193
-11
lines changed

8 files changed

+193
-11
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
* This code is free software; you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License version 2 only, as
6+
* published by the Free Software Foundation. Codename One designates this
7+
* particular file as subject to the "Classpath" exception as provided
8+
* by Oracle in the LICENSE file that accompanied this code.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Codename One through http://www.codenameone.com/ if you
21+
* need additional information or have any questions.
22+
*/
23+
package com.codename1.annotations;
24+
25+
import java.lang.annotation.ElementType;
26+
import java.lang.annotation.Retention;
27+
import java.lang.annotation.RetentionPolicy;
28+
import java.lang.annotation.Target;
29+
30+
/// Indicates that a class has a known concrete implementation that ParparVM can
31+
/// target directly in native (C/Objective-C) pipelines.
32+
///
33+
/// When present, the translator may bypass virtual lookup when invoking methods
34+
/// on this type by preferring the concrete class provided in {@link #name()},
35+
/// and falling back to the annotated type implementation if the concrete class
36+
/// doesn't implement the method.
37+
@Retention(RetentionPolicy.CLASS)
38+
@Target(ElementType.TYPE)
39+
public @interface Concrete {
40+
/// The fully-qualified class name of the concrete implementation to prefer
41+
/// during ParparVM native translation.
42+
String name();
43+
}

CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
package com.codename1.impl;
2525

26+
import com.codename1.annotations.Concrete;
2627
import com.codename1.capture.VideoCaptureConstraints;
2728
import com.codename1.codescan.CodeScanner;
2829
import com.codename1.components.AudioRecorderComponent;
@@ -109,6 +110,7 @@
109110
/// Display specifically for key, pointer events and screen resolution.
110111
///
111112
/// @author Shai Almog
113+
@Concrete(name = "com.codename1.impl.ios.IOSImplementation")
112114
public abstract class CodenameOneImplementation {
113115
/// Indicates the range of "hard" RTL bidi characters in unicode
114116
private static final int RTL_RANGE_BEGIN = 0x590;

docs/developer-guide/performance.asciidoc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,28 @@ For ParparVM-generated native code, we now support method-level optimization hin
6161

6262
TIP: Use these on methods that are both performance-critical and well-covered by tests. These annotations intentionally trade runtime safety diagnostics for speed.
6363

64+
===== Class-level concrete implementation hints
65+
66+
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:
67+
68+
* `@Concrete(name="fully.qualified.ConcreteClassName")` +
69+
Allows the translator to bypass virtual table lookup for `invokevirtual` calls on the annotated base class.
70+
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.
71+
72+
This is useful for platform abstraction classes where one implementation is guaranteed in the native pipeline.
73+
74+
For example, Codename One annotates:
75+
76+
[source,java]
77+
----
78+
@Concrete(name = "com.codename1.impl.ios.IOSImplementation")
79+
public abstract class CodenameOneImplementation {
80+
// ...
81+
}
82+
----
83+
84+
NOTE: This hint is intended for ParparVM native translation and does not apply to the JavaScript backend.
85+
6486
===== Fast method-stack path
6587

6688
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.

vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeClass.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public void setIsAnnotation(boolean isAnnotation) {
6666
private String clsName;
6767
private String originalClassName;
6868
private String baseClass;
69+
private String concreteClass;
6970
private List<String> baseInterfaces;
7071
private boolean isInterface;
7172
private boolean isAbstract;
@@ -342,6 +343,11 @@ private BytecodeMethod findDeclaredMethod(String name, String desc) {
342343
return null;
343344
}
344345

346+
public boolean hasDeclaredNonAbstractMethod(String name, String desc) {
347+
BytecodeMethod declaredMethod = findDeclaredMethod(name, desc);
348+
return declaredMethod != null && !declaredMethod.isAbstract();
349+
}
350+
345351
public void unmark() {
346352
marked = false;
347353
}
@@ -1891,6 +1897,14 @@ public String getBaseClass() {
18911897
return baseClass;
18921898
}
18931899

1900+
public String getConcreteClass() {
1901+
return concreteClass;
1902+
}
1903+
1904+
public void setConcreteClass(String concreteClass) {
1905+
this.concreteClass = concreteClass;
1906+
}
1907+
18941908
public void setSourceFile(String sourceFile) {
18951909
this.sourceFile = sourceFile;
18961910
}

vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,6 +1473,7 @@ public void setMaxes(int maxStack, int maxLocals) {
14731473

14741474
private void addInstruction(Instruction i) {
14751475
instructions.add(i);
1476+
i.setMethod(this);
14761477
i.addDependencies(dependentClasses);
14771478
if (dependencyGraph != null) {
14781479
String methodUsed = i.getMethodUsed();

vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public class Parser extends ClassVisitor {
5151
private static final String DISABLE_DEBUG_INFO_ANNOTATION = "Lcom/codename1/annotations/DisableDebugInfo;";
5252
private static final String DISABLE_NULL_AND_ARRAY_BOUNDS_CHECKS_ANNOTATION =
5353
"Lcom/codename1/annotations/DisableNullChecksAndArrayBoundsChecks;";
54+
private static final String CONCRETE_ANNOTATION = "Lcom/codename1/annotations/Concrete;";
5455
private ByteCodeClass cls;
5556
private String clsName;
5657
private static String[] nativeSources;
@@ -699,6 +700,17 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str
699700

700701
@Override
701702
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
703+
if (CONCRETE_ANNOTATION.equals(desc)) {
704+
return new AnnotationVisitorWrapper(super.visitAnnotation(desc, visible)) {
705+
@Override
706+
public void visit(String name, Object value) {
707+
if ("name".equals(name) && value instanceof String) {
708+
cls.setConcreteClass(((String)value).replace('.', '/'));
709+
}
710+
super.visit(name, value);
711+
}
712+
};
713+
}
702714
return new AnnotationVisitorWrapper(super.visitAnnotation(desc, visible));
703715
}
704716

vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,26 @@ public String getMethodUsed() {
9191

9292
@Override
9393
public void addDependencies(List<String> dependencyList) {
94+
String dependencyOwner = owner;
95+
if (origOpcode == Opcodes.INVOKEVIRTUAL) {
96+
ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_'));
97+
String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc);
98+
if (resolvedConcreteOwner != null) {
99+
dependencyOwner = resolvedConcreteOwner;
100+
}
101+
}
94102
String t = owner.replace('.', '_').replace('/', '_').replace('$', '_');
95103
t = unarray(t);
96104
if(t != null && !dependencyList.contains(t)) {
97105
dependencyList.add(t);
98106
}
107+
if (!owner.equals(dependencyOwner)) {
108+
String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_');
109+
concreteDependency = unarray(concreteDependency);
110+
if (concreteDependency != null && !dependencyList.contains(concreteDependency)) {
111+
dependencyList.add(concreteDependency);
112+
}
113+
}
99114

100115
StringBuilder bld = new StringBuilder();
101116
if(origOpcode != Opcodes.INVOKEINTERFACE && origOpcode != Opcodes.INVOKEVIRTUAL) {
@@ -134,7 +149,23 @@ private String findActualOwner(ByteCodeClass bc) {
134149
}
135150
return findActualOwner(bc.getBaseClassObject());
136151
}
137-
152+
153+
private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) {
154+
if (ownerClass == null || ownerClass.getConcreteClass() == null || getMethod() == null) {
155+
return null;
156+
}
157+
String currentClass = getMethod().getClsName();
158+
String ownerName = ownerClass.getClsName();
159+
if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) {
160+
return null;
161+
}
162+
ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_'));
163+
if (concreteClass != null && concreteClass.hasDeclaredNonAbstractMethod(name, desc)) {
164+
return concreteClass.getClsName();
165+
}
166+
return null;
167+
}
168+
138169
public boolean methodHasReturnValue() {
139170
return BytecodeMethod.appendMethodSignatureSuffixFromDesc(desc, new StringBuilder(), new ArrayList<>()) != null;
140171
}
@@ -160,6 +191,7 @@ public boolean appendExpression(StringBuilder b) {
160191
owner = Util.resolveInvokeSpecialOwner(owner, name, desc);
161192
}
162193

194+
String invokeOwner = owner;
163195
StringBuilder bld = new StringBuilder();
164196
boolean isVirtualCall = false;
165197
if(origOpcode == Opcodes.INVOKEINTERFACE || origOpcode == Opcodes.INVOKEVIRTUAL) {
@@ -176,6 +208,12 @@ public boolean appendExpression(StringBuilder b) {
176208
} else {
177209
if (bc.isMethodPrivate(name, desc)) {
178210
isVirtual = false;
211+
} else {
212+
String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc);
213+
if (resolvedConcreteOwner != null) {
214+
invokeOwner = resolvedConcreteOwner;
215+
isVirtual = false;
216+
}
179217
}
180218
}
181219

@@ -191,12 +229,12 @@ public boolean appendExpression(StringBuilder b) {
191229
if(origOpcode == Opcodes.INVOKESTATIC) {
192230
// find the actual class of the static method to work around javac not defining it correctly
193231
ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_'));
194-
owner = findActualOwner(bc);
232+
invokeOwner = findActualOwner(bc);
195233
}
196-
if (owner.startsWith("[")) {
234+
if (invokeOwner.startsWith("[")) {
197235
bld.append("java_lang_Object");
198236
} else{
199-
bld.append(owner.replace('/', '_').replace('$', '_'));
237+
bld.append(invokeOwner.replace('/', '_').replace('$', '_'));
200238
}
201239
bld.append("_");
202240
if(name.equals("<init>")) {
@@ -264,7 +302,9 @@ public void appendInstruction(StringBuilder b) {
264302
owner = Util.resolveInvokeSpecialOwner(owner, name, desc);
265303
}
266304

305+
String invokeOwner = owner;
267306
StringBuilder bld = new StringBuilder();
307+
boolean isVirtualCall = false;
268308
if(origOpcode == Opcodes.INVOKEINTERFACE || origOpcode == Opcodes.INVOKEVIRTUAL) {
269309
b.append(" ");
270310

@@ -279,12 +319,19 @@ public void appendInstruction(StringBuilder b) {
279319
} else {
280320
if (bc.isMethodPrivate(name, desc)) {
281321
isVirtual = false;
322+
} else {
323+
String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc);
324+
if (resolvedConcreteOwner != null) {
325+
invokeOwner = resolvedConcreteOwner;
326+
isVirtual = false;
327+
}
282328
}
283329
}
284330

285331
}
286332
if (isVirtual) {
287333
bld.append("virtual_");
334+
isVirtualCall = true;
288335
}
289336
} else {
290337
b.append(" ");
@@ -293,12 +340,12 @@ public void appendInstruction(StringBuilder b) {
293340
if(origOpcode == Opcodes.INVOKESTATIC) {
294341
// find the actual class of the static method to work around javac not defining it correctly
295342
ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_'));
296-
owner = findActualOwner(bc);
343+
invokeOwner = findActualOwner(bc);
297344
}
298-
if (owner.startsWith("[")) {
345+
if (invokeOwner.startsWith("[")) {
299346
bld.append("java_lang_Object");
300347
} else{
301-
bld.append(owner.replace('/', '_').replace('$', '_'));
348+
bld.append(invokeOwner.replace('/', '_').replace('$', '_'));
302349
}
303350
bld.append("_");
304351
if(name.equals("<init>")) {
@@ -313,6 +360,9 @@ public void appendInstruction(StringBuilder b) {
313360
bld.append("__");
314361
ArrayList<String> args = new ArrayList<>();
315362
String returnVal = BytecodeMethod.appendMethodSignatureSuffixFromDesc(desc, bld, args);
363+
if (isVirtualCall) {
364+
BytecodeMethod.addVirtualMethodsInvoked(bld.substring("virtual_".length()));
365+
}
316366
int numLiteralArgs = this.getNumLiteralArgs();
317367
if (numLiteralArgs > 0) {
318368
b.append("/* CustomInvoke */");

vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,26 @@ private String getCMethodName() {
8989

9090
@Override
9191
public void addDependencies(List<String> dependencyList) {
92+
String dependencyOwner = owner;
93+
if (opcode == Opcodes.INVOKEVIRTUAL) {
94+
ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_'));
95+
String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc);
96+
if (resolvedConcreteOwner != null) {
97+
dependencyOwner = resolvedConcreteOwner;
98+
}
99+
}
92100
String t = owner.replace('.', '_').replace('/', '_').replace('$', '_');
93101
t = unarray(t);
94102
if(t != null && !dependencyList.contains(t)) {
95103
dependencyList.add(t);
96104
}
105+
if (!owner.equals(dependencyOwner)) {
106+
String concreteDependency = dependencyOwner.replace('.', '_').replace('/', '_').replace('$', '_');
107+
concreteDependency = unarray(concreteDependency);
108+
if (concreteDependency != null && !dependencyList.contains(concreteDependency)) {
109+
dependencyList.add(concreteDependency);
110+
}
111+
}
97112

98113
StringBuilder bld = new StringBuilder();
99114
if(opcode != Opcodes.INVOKEINTERFACE && opcode != Opcodes.INVOKEVIRTUAL) {
@@ -132,7 +147,23 @@ private String findActualOwner(ByteCodeClass bc) {
132147
}
133148
return findActualOwner(bc.getBaseClassObject());
134149
}
135-
150+
151+
private String resolveConcreteInvokeOwner(ByteCodeClass ownerClass) {
152+
if (ownerClass == null || ownerClass.getConcreteClass() == null || getMethod() == null) {
153+
return null;
154+
}
155+
String currentClass = getMethod().getClsName();
156+
String ownerName = ownerClass.getClsName();
157+
if (ownerName.equals(currentClass) || currentClass.startsWith(ownerName + "_")) {
158+
return null;
159+
}
160+
ByteCodeClass concreteClass = Parser.getClassObject(ownerClass.getConcreteClass().replace('/', '_').replace('$', '_'));
161+
if (concreteClass != null && concreteClass.hasDeclaredNonAbstractMethod(name, desc)) {
162+
return concreteClass.getClsName();
163+
}
164+
return null;
165+
}
166+
136167
@Override
137168
public void appendInstruction(StringBuilder b) {
138169
// special case for clone on an array which isn't a real method invocation
@@ -144,6 +175,7 @@ public void appendInstruction(StringBuilder b) {
144175
owner = Util.resolveInvokeSpecialOwner(owner, name, desc);
145176
}
146177

178+
String invokeOwner = owner;
147179
StringBuilder bld = new StringBuilder();
148180
boolean isVirtualCall = false;
149181
if(opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEVIRTUAL) {
@@ -161,6 +193,12 @@ public void appendInstruction(StringBuilder b) {
161193
} else {
162194
if (bc.isMethodPrivate(name, desc)) {
163195
isVirtual = false;
196+
} else {
197+
String resolvedConcreteOwner = resolveConcreteInvokeOwner(bc);
198+
if (resolvedConcreteOwner != null) {
199+
invokeOwner = resolvedConcreteOwner;
200+
isVirtual = false;
201+
}
164202
}
165203
}
166204
}
@@ -175,14 +213,14 @@ public void appendInstruction(StringBuilder b) {
175213
if(opcode == Opcodes.INVOKESTATIC) {
176214
// find the actual class of the static method to work around javac not defining it correctly
177215
ByteCodeClass bc = Parser.getClassObject(owner.replace('/', '_').replace('$', '_'));
178-
owner = findActualOwner(bc);
216+
invokeOwner = findActualOwner(bc);
179217
}
180-
if (owner.startsWith("[")) {
218+
if (invokeOwner.startsWith("[")) {
181219
// Kotlin seems to generate calls to toString() on arrays using the array class
182220
// as an owner. We'll just change this to java_lang_Object instead.
183221
bld.append("java_lang_Object");
184222
} else{
185-
bld.append(owner.replace('/', '_').replace('$', '_'));
223+
bld.append(invokeOwner.replace('/', '_').replace('$', '_'));
186224
}
187225
bld.append("_");
188226
if(name.equals("<init>")) {

0 commit comments

Comments
 (0)