Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions CodenameOne/src/com/codename1/annotations/Concrete.java
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 22 additions & 0 deletions docs/developer-guide/performance.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public void setIsAnnotation(boolean isAnnotation) {
private String clsName;
private String originalClassName;
private String baseClass;
private String concreteClass;
private List<String> baseInterfaces;
private boolean isInterface;
private boolean isAbstract;
Expand Down Expand Up @@ -342,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;
}
Expand Down Expand Up @@ -1891,6 +1897,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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,26 @@ public String getMethodUsed() {

@Override
public void addDependencies(List<String> 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) {
Expand Down Expand Up @@ -134,7 +149,23 @@ 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;
}
Expand All @@ -160,6 +191,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) {
Expand All @@ -176,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;
}
}
}

Expand All @@ -191,12 +229,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("<init>")) {
Expand Down Expand Up @@ -264,7 +302,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(" ");

Expand All @@ -279,12 +319,19 @@ 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;
}
}
}

}
if (isVirtual) {
bld.append("virtual_");
isVirtualCall = true;
}
} else {
b.append(" ");
Expand All @@ -293,12 +340,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("<init>")) {
Expand All @@ -313,6 +360,9 @@ public void appendInstruction(StringBuilder b) {
bld.append("__");
ArrayList<String> 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 */");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,26 @@ private String getCMethodName() {

@Override
public void addDependencies(List<String> 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) {
Expand Down Expand Up @@ -132,7 +147,23 @@ 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
Expand All @@ -144,6 +175,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) {
Expand All @@ -161,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;
}
}
}
}
Expand All @@ -175,14 +213,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("<init>")) {
Expand Down
Loading