diff --git a/CodenameOne/src/com/codename1/annotations/DisableDebugInfo.java b/CodenameOne/src/com/codename1/annotations/DisableDebugInfo.java new file mode 100644 index 0000000000..23a11cd503 --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/DisableDebugInfo.java @@ -0,0 +1,14 @@ +package com.codename1.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method so ParparVM omits emitted debug line information. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface DisableDebugInfo { +} diff --git a/CodenameOne/src/com/codename1/annotations/DisableNullChecksAndArrayBoundsChecks.java b/CodenameOne/src/com/codename1/annotations/DisableNullChecksAndArrayBoundsChecks.java new file mode 100644 index 0000000000..4c38e4de6e --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/DisableNullChecksAndArrayBoundsChecks.java @@ -0,0 +1,14 @@ +package com.codename1.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a method so ParparVM omits emitted null and array bounds checks. + */ +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.METHOD) +public @interface DisableNullChecksAndArrayBoundsChecks { +} diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index 21a0da0f83..4e042537bd 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -19,6 +19,10 @@ package com.codename1.util; +import com.codename1.annotations.DisableDebugInfo; +import com.codename1.annotations.DisableNullChecksAndArrayBoundsChecks; + + /// This class implements Base64 encoding/decoding functionality /// as specified in RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt). public abstract class Base64 { @@ -34,18 +38,25 @@ public abstract class Base64 { '4', '5', '6', '7', '8', '9', '+', '/'}; private static final byte[] decodeMap = new byte[256]; + private static final int[] decodeMapInt = new int[256]; static { for (int i = 0; i < decodeMap.length; i++) { decodeMap[i] = (byte) DECODE_INVALID; + decodeMapInt[i] = DECODE_INVALID; } for (int i = 0; i < map.length; i++) { decodeMap[map[i] & 0xff] = (byte) i; + decodeMapInt[map[i] & 0xff] = i; } decodeMap['\n'] = (byte) DECODE_WHITESPACE; decodeMap['\r'] = (byte) DECODE_WHITESPACE; decodeMap[' '] = (byte) DECODE_WHITESPACE; decodeMap['\t'] = (byte) DECODE_WHITESPACE; + decodeMapInt['\n'] = DECODE_WHITESPACE; + decodeMapInt['\r'] = DECODE_WHITESPACE; + decodeMapInt[' '] = DECODE_WHITESPACE; + decodeMapInt['\t'] = DECODE_WHITESPACE; } public static byte[] decode(byte[] in) { @@ -89,6 +100,8 @@ public static byte[] decode(byte[] in, int len) { * @param out destination buffer * @return decoded length, or {@code -1} for invalid Base64 */ + @DisableDebugInfo + @DisableNullChecksAndArrayBoundsChecks public static int decode(byte[] in, int len, byte[] out) { if (len == 0) { return 0; @@ -103,7 +116,7 @@ public static int decode(byte[] in, int len, byte[] out) { int end = len; while (end > 0) { int chr = in[end - 1] & 0xff; - if (decodeMap[chr] == DECODE_WHITESPACE) { + if (decodeMapInt[chr] == DECODE_WHITESPACE) { end--; continue; } @@ -121,7 +134,7 @@ public static int decode(byte[] in, int len, byte[] out) { if (chr == '=') { break; } - int value = decodeMap[chr]; + int value = decodeMapInt[chr]; if (value == DECODE_WHITESPACE) { continue; } @@ -148,7 +161,7 @@ public static int decode(byte[] in, int len, byte[] out) { if (chr == '=') { break; } - int bits = decodeMap[chr]; + int bits = decodeMapInt[chr]; if (bits == DECODE_WHITESPACE) { continue; } @@ -184,6 +197,8 @@ public static int decode(byte[] in, byte[] out) { return decode(in, in.length, out); } + @DisableDebugInfo + @DisableNullChecksAndArrayBoundsChecks private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((len & 0x3) != 0) { return -1; @@ -207,8 +222,8 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { throw new IllegalArgumentException("Output buffer too small for decoded data"); } int outIndex = 0; - byte[] decodeMapLocal = decodeMap; int fullLen = len - (pad > 0 ? 4 : 0); + int[] decodeMapLocal = decodeMapInt; for (int i = 0; i < fullLen; i += 4) { int c0 = in[i] & 0xff; @@ -334,6 +349,8 @@ public static String encodeNoNewline(byte[] in) { * @param out destination buffer * @return number of bytes written to {@code out} */ + @DisableDebugInfo + @DisableNullChecksAndArrayBoundsChecks public static int encodeNoNewline(byte[] in, byte[] out) { int inputLength = in.length; int outputLength = ((inputLength + 2) / 3) * 4; diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index e787ec7e78..d2a9e6666a 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -45,6 +45,60 @@ The simulator contains some tools to measure performance overhead of a specific * *On some platforms mutable images are slow* - mutable images are images you can draw on (using `getGraphics()`). On some platforms they perform quite badly (e.g. iOS) and should generally be avoided. You can check if mutable images are fast in a platform using `Display.areMutableImagesFast()` * * Make components either transparent or opaque * - a translucent component must paint it's parent every time. This can be expensive. An opaque component might have margins that would require that we paint the parent so there is often overdraw in such cases (overdraw means the same pixel being painted twice). +==== ParparVM Native Translation Performance Hints + +For ParparVM-generated native code, we now support method-level optimization hints via annotations. These can provide very good wins in hot code paths, but they come with tradeoffs and should be applied surgically. + +===== Method-level codegen hints + +* `@DisableDebugInfo` + + Suppresses generated line/debug metadata for the annotated method. + This can reduce generated C size and remove some per-instruction debug overhead. + +* `@DisableNullChecksAndArrayBoundsChecks` + + Suppresses generated null and array-bounds checks for the annotated method. + This can significantly reduce branch-heavy code in tight loops. + +TIP: Use these only on methods that are both performance-critical and well-covered by tests. These annotations intentionally trade runtime safety diagnostics for speed. + +===== 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. + +In practice, this tends to help for: + +* Small, hot methods. +* Methods without monitor usage / exception-heavy flow. +* Methods with straightforward control flow and low instruction complexity. + +Tradeoffs: + +* Overly broad fast-path eligibility can regress performance if extra branches or memory writes are introduced. +* Primitive-only fast-frame variants may not always outperform a straightforward full clear on all targets/compilers. + +TIP: Benchmark representative workloads after enabling fast-stack behavior. Keep eligibility conservative and expand only where measurement shows consistent gains. + +===== Base64-style hot-loop guidelines + +For low-level loops (e.g. Base64 encode/decode): + +* Prefer simple loop bodies with predictable branches. +* Cache decode/lookup tables in primitive arrays (`int[]` lookup tables can reduce per-iteration conversion overhead). +* Avoid adding “defensive” branches in the inner-most loop unless they are required for correctness in production inputs. + +===== Build configuration matters + +When benchmarking translator output, ensure native projects are compiled with optimization enabled (e.g. CMake `Release` builds). Debug/default builds can hide improvements or produce misleading regressions. + +If you are using the integration test harness, make sure CMake is configured with: + +[source] +---- +-DCMAKE_BUILD_TYPE=Release +---- + +Without this setting, comparison between Java and ParparVM native output is often noisy and can lead to incorrect optimization conclusions. + === Performance Monitor The Performance Monitor tool can be accessible via the #Simulator# -> #Performance Monitor# menu option in the simulator. This launches the following UI that can help you improve application performance: diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index b13e6c5446..5b3c8bfebe 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.h +++ b/vm/ByteCodeTranslator/src/cn1_globals.h @@ -879,6 +879,8 @@ extern void throwException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT exceptionArg); extern JAVA_INT throwException_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT exceptionArg); extern JAVA_BOOLEAN throwException_R_boolean(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT exceptionArg); extern JAVA_OBJECT __NEW_java_lang_NullPointerException(CODENAME_ONE_THREAD_STATE); +extern JAVA_OBJECT __NEW_INSTANCE_java_lang_NullPointerException(CODENAME_ONE_THREAD_STATE); +extern JAVA_OBJECT __NEW_INSTANCE_java_lang_StackOverflowError(CODENAME_ONE_THREAD_STATE); extern JAVA_OBJECT __NEW_java_lang_ArrayIndexOutOfBoundsException(CODENAME_ONE_THREAD_STATE); extern JAVA_VOID java_lang_ArrayIndexOutOfBoundsException___INIT_____int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_INT __cn1Arg1); extern void throwArrayIndexOutOfBoundsException(CODENAME_ONE_THREAD_STATE, int index); @@ -1129,6 +1131,31 @@ extern JAVA_OBJECT newStringFromCString(CODENAME_ONE_THREAD_STATE, const char *s extern void initConstantPool(); extern void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize, int classNameId, int methodNameId); +static inline void cn1_init_method_stack_fast(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize, JAVA_BOOLEAN fullClear) { +#ifdef CN1_INCLUDE_NPE_CHECKS + if(__cn1ThisObject == JAVA_NULL) { + THROW_NULL_POINTER_EXCEPTION(); + } +#endif + if (threadStateData->callStackOffset >= CN1_STACK_OVERFLOW_CALL_DEPTH_LIMIT - 1) { + throwException(threadStateData, __NEW_INSTANCE_java_lang_StackOverflowError(threadStateData)); + return; + } + if (fullClear) { + memset(&threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset], 0, + sizeof(struct elementStruct) * (localsStackSize + stackSize)); + } else { + /* + * Primitive-only fast frames intentionally use the same memset strategy. + * A per-slot type-only loop was measurably slower in benchmarks and did + * not improve generated-code performance. + */ + memset(&threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset], 0, + sizeof(struct elementStruct) * (localsStackSize + stackSize)); + } + threadStateData->threadObjectStackOffset += localsStackSize + stackSize; + threadStateData->callStackOffset++; +} // we need to zero out the values with memset otherwise we will run into a problem // when invoking release on pre-existing object which might be garbage @@ -1150,6 +1177,46 @@ extern void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObje const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ int methodBlockOffset = threadStateData->tryBlockOffset; +#define DEFINE_METHOD_STACK_FAST_REF(stackSize, localsStackSize, spPosition) \ + const int cn1LocalsBeginInThread = threadStateData->threadObjectStackOffset; \ + struct elementStruct* locals = &threadStateData->threadObjectStack[cn1LocalsBeginInThread]; \ + struct elementStruct* stack = &threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset + localsStackSize]; \ + struct elementStruct* SP = &stack[spPosition]; \ + cn1_init_method_stack_fast(threadStateData, (JAVA_OBJECT)1, stackSize, localsStackSize, JAVA_TRUE); \ + const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ + int methodBlockOffset = threadStateData->tryBlockOffset; + +#define DEFINE_INSTANCE_METHOD_STACK_FAST_REF(stackSize, localsStackSize, spPosition) \ + const int cn1LocalsBeginInThread = threadStateData->threadObjectStackOffset; \ + struct elementStruct* locals = &threadStateData->threadObjectStack[cn1LocalsBeginInThread]; \ + struct elementStruct* stack = &threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset + localsStackSize]; \ + struct elementStruct* SP = &stack[spPosition]; \ + cn1_init_method_stack_fast(threadStateData, __cn1ThisObject, stackSize, localsStackSize, JAVA_TRUE); \ + const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ + int methodBlockOffset = threadStateData->tryBlockOffset; + +#define DEFINE_METHOD_STACK_FAST_PRIMITIVE(stackSize, localsStackSize, spPosition) \ + const int cn1LocalsBeginInThread = threadStateData->threadObjectStackOffset; \ + struct elementStruct* locals = &threadStateData->threadObjectStack[cn1LocalsBeginInThread]; \ + struct elementStruct* stack = &threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset + localsStackSize]; \ + struct elementStruct* SP = &stack[spPosition]; \ + cn1_init_method_stack_fast(threadStateData, (JAVA_OBJECT)1, stackSize, localsStackSize, JAVA_FALSE); \ + const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ + int methodBlockOffset = threadStateData->tryBlockOffset; + +#define DEFINE_INSTANCE_METHOD_STACK_FAST_PRIMITIVE(stackSize, localsStackSize, spPosition) \ + const int cn1LocalsBeginInThread = threadStateData->threadObjectStackOffset; \ + struct elementStruct* locals = &threadStateData->threadObjectStack[cn1LocalsBeginInThread]; \ + struct elementStruct* stack = &threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset + localsStackSize]; \ + struct elementStruct* SP = &stack[spPosition]; \ + cn1_init_method_stack_fast(threadStateData, __cn1ThisObject, stackSize, localsStackSize, JAVA_FALSE); \ + const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ + int methodBlockOffset = threadStateData->tryBlockOffset; + +#define CN1_FAST_RETURN_RELEASE() \ + threadStateData->threadObjectStackOffset = cn1LocalsBeginInThread; \ + threadStateData->callStackOffset--; + #if defined(__APPLE__) && defined(__OBJC__) @class NSString; diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 48e68ac973..d05501af9c 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -108,6 +108,10 @@ public static void setDependencyGraph(MethodDependencyGraph dependencyGraph) { private String desc; private boolean eliminated; private boolean barebone; + private boolean disableDebugInfo; + private boolean disableNullAndArrayBoundsChecks; + private boolean fastMethodStackInUse; + private boolean fastMethodStackPrimitiveOnly; static boolean optimizerOn; @@ -122,6 +126,22 @@ public boolean isBarebone() { return barebone; } + public boolean isDisableDebugInfo() { + return disableDebugInfo; + } + + public void setDisableDebugInfo(boolean disableDebugInfo) { + this.disableDebugInfo = disableDebugInfo; + } + + public boolean isDisableNullAndArrayBoundsChecks() { + return disableNullAndArrayBoundsChecks; + } + + public void setDisableNullAndArrayBoundsChecks(boolean disableNullAndArrayBoundsChecks) { + this.disableNullAndArrayBoundsChecks = disableNullAndArrayBoundsChecks; + } + private boolean checkBarebone() { if(synchronizedMethod || nativeMethod || hasExceptionHandlingOrMethodCalls() || localVariables.size() > 0) { return false; @@ -268,6 +288,74 @@ private boolean checkBarebone() { } return true; } + + private boolean canUseFastMethodStack() { + if (synchronizedMethod || nativeMethod || abstractMethod) { + return false; + } + for (Instruction instruction : instructions) { + if (instruction instanceof TryCatch + || instruction instanceof Invoke + || instruction instanceof CustomInvoke + || instruction instanceof Field + || instruction instanceof TypeInstruction + || instruction instanceof MultiArray + || instruction instanceof CustomIntruction) { + return false; + } + if (instruction instanceof ArrayLoadExpression && !disableNullAndArrayBoundsChecks) { + return false; + } + if (instruction instanceof BasicInstruction) { + int op = instruction.getOpcode(); + if (op == Opcodes.MONITORENTER || op == Opcodes.MONITOREXIT + || op == Opcodes.ATHROW + || op == Opcodes.IDIV || op == Opcodes.LDIV || op == Opcodes.IREM || op == Opcodes.LREM + || op == Opcodes.ARRAYLENGTH + || (!disableNullAndArrayBoundsChecks && (op >= Opcodes.IALOAD && op <= Opcodes.SALOAD)) + || (!disableNullAndArrayBoundsChecks && (op >= Opcodes.IASTORE && op <= Opcodes.SASTORE)) + || (!disableNullAndArrayBoundsChecks && (op == Opcodes.AALOAD || op == Opcodes.AASTORE + || op == Opcodes.BALOAD || op == Opcodes.BASTORE || op == Opcodes.CALOAD || op == Opcodes.CASTORE)) + || op == Opcodes.NEWARRAY) { + return false; + } + } + } + return true; + } + + private boolean isPrimitiveOnlyFastFrameCandidate() { + for (ByteCodeMethodArg arg : arguments) { + if (arg.getQualifier() == 'o') { + return false; + } + } + if (!returnType.isVoid() && returnType.getQualifier() == 'o') { + return false; + } + if (!staticMethod) { + return false; + } + for (Instruction instruction : instructions) { + if (instruction instanceof VarOp) { + int op = instruction.getOpcode(); + if (op == Opcodes.ALOAD || op == Opcodes.ASTORE) { + return false; + } + } + if (instruction instanceof BasicInstruction) { + int op = instruction.getOpcode(); + if (op == Opcodes.ARETURN || op == Opcodes.ACONST_NULL || op == Opcodes.AALOAD || op == Opcodes.AASTORE) { + return false; + } + } + } + return true; + } + + public boolean useFastReturnRelease() { + return fastMethodStackInUse && !TryCatch.isTryCatchInMethod(); + } public BytecodeMethod(String clsName, int access, String name, String desc, String signature, String[] exceptions) { methodName = name; @@ -847,6 +935,7 @@ public void appendMethodC(StringBuilder b) { } b.append(declaration); + boolean fastMethodStackCandidate = canUseFastMethodStack(); boolean hasInstructions = true; if(optimizerOn) { @@ -863,7 +952,10 @@ public void appendMethodC(StringBuilder b) { String variableName = lv.getQualifier() + "locals_"+lv.getIndex()+"_"; if (!added.contains(variableName) && (barebone || lv.getQualifier() != 'o')) { added.add(variableName); - b.append(" volatile "); + b.append(" "); + if (!disableDebugInfo) { + b.append("volatile "); + } switch (lv.getQualifier()) { case 'i' : b.append("JAVA_INT"); break; @@ -880,25 +972,58 @@ public void appendMethodC(StringBuilder b) { } } + boolean useFastMethodStack = !barebone && fastMethodStackCandidate; + boolean usePrimitiveFastFrame = useFastMethodStack && isPrimitiveOnlyFastFrameCandidate(); + fastMethodStackInUse = useFastMethodStack; + fastMethodStackPrimitiveOnly = usePrimitiveFastFrame; if(!barebone) { if(staticMethod) { if(methodName.equals("__CLINIT__")) { - b.append(" DEFINE_METHOD_STACK("); + if (useFastMethodStack) { + if (usePrimitiveFastFrame) { + b.append(" DEFINE_METHOD_STACK_FAST_PRIMITIVE("); + } else { + b.append(" DEFINE_METHOD_STACK_FAST_REF("); + } + } else { + b.append(" DEFINE_METHOD_STACK("); + } } else { - b.append(" __STATIC_INITIALIZER_"); + b.append(" if (!class__"); + b.append(clsName.replace('/', '_').replace('$', '_')); + b.append(".initialized) __STATIC_INITIALIZER_"); b.append(clsName.replace('/', '_').replace('$', '_')); - b.append("(threadStateData);\n DEFINE_METHOD_STACK("); + if (useFastMethodStack) { + if (usePrimitiveFastFrame) { + b.append("(threadStateData);\n DEFINE_METHOD_STACK_FAST_PRIMITIVE("); + } else { + b.append("(threadStateData);\n DEFINE_METHOD_STACK_FAST_REF("); + } + } else { + b.append("(threadStateData);\n DEFINE_METHOD_STACK("); + } } } else { - b.append(" DEFINE_INSTANCE_METHOD_STACK("); + if (useFastMethodStack) { + if (usePrimitiveFastFrame) { + b.append(" DEFINE_INSTANCE_METHOD_STACK_FAST_PRIMITIVE("); + } else { + b.append(" DEFINE_INSTANCE_METHOD_STACK_FAST_REF("); + } + } else { + b.append(" DEFINE_INSTANCE_METHOD_STACK("); + } } b.append(maxStack); b.append(", "); b.append(maxLocals); - b.append(", 0, "); - b.append(Parser.addToConstantPool(clsName)); - b.append(", "); - b.append(Parser.addToConstantPool(methodName)); + b.append(", 0"); + if (!useFastMethodStack) { + b.append(", "); + b.append(Parser.addToConstantPool(clsName)); + b.append(", "); + b.append(Parser.addToConstantPool(methodName)); + } b.append(");\n"); } else { b.append(" struct elementStruct* SP = &threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset];\n"); @@ -1315,6 +1440,9 @@ public void addTryCatchBlock(Label start, Label end, Label handler, String type) } public void addLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + if (disableDebugInfo) { + return; + } //addInstruction(0, new LocalVariable(name, desc, signature, start, end, index)); localVariables.add(new LocalVariable(name, desc, signature, start, end, index)); } @@ -1324,6 +1452,9 @@ public void setSourceFile(String sourceFile) { } public void addDebugInfo(int line) { + if (disableDebugInfo) { + return; + } addInstruction(new LineNumber(sourceFile, line)); } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java index 591aa2bff1..2b888883d9 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java @@ -48,6 +48,9 @@ * @author Shai Almog */ 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 ByteCodeClass cls; private String clsName; private static String[] nativeSources; @@ -1197,6 +1200,11 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (DISABLE_DEBUG_INFO_ANNOTATION.equals(desc)) { + mtd.setDisableDebugInfo(true); + } else if (DISABLE_NULL_AND_ARRAY_BOUNDS_CHECKS_ANNOTATION.equals(desc)) { + mtd.setDisableNullAndArrayBoundsChecks(true); + } if (mv == null) return null; return new AnnotationVisitorWrapper(super.visitAnnotation(desc, visible)); } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/ArrayLoadExpression.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/ArrayLoadExpression.java index 4f9d120dcd..cd3e0c1471 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/ArrayLoadExpression.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/ArrayLoadExpression.java @@ -184,7 +184,9 @@ public boolean assignTo(String varName, StringBuilder sb) { b.append("{\n"); b.append(" JAVA_OBJECT __cn1ArrayTmp = ").append(arrayExpr).append(";\n"); b.append(" JAVA_INT __cn1IndexTmp = ").append(indexExpr).append(";\n"); - b.append(" CHECK_ARRAY_ACCESS_WITH_ARGS(__cn1ArrayTmp, __cn1IndexTmp);\n"); + if (getMethod() == null || !getMethod().isDisableNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS_WITH_ARGS(__cn1ArrayTmp, __cn1IndexTmp);\n"); + } b.append(" ").append(varName).append(" = ((").append(arrayDataType).append("*) (*(JAVA_ARRAY)__cn1ArrayTmp).data)[__cn1IndexTmp];\n"); b.append("}\n"); sb.append(b); diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java index 779d247bec..06e60b08d7 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java @@ -74,6 +74,10 @@ public static boolean isSynchronizedMethod() { return synchronizedMethod; } + private boolean shouldEmitNullAndArrayBoundsChecks() { + return getMethod() == null || !getMethod().isDisableNullAndArrayBoundsChecks(); + } + @Override public boolean isConstant() { switch (opcode) { @@ -167,92 +171,168 @@ public void appendInstruction(StringBuilder b, List instructions) { break; case Opcodes.BALOAD: - b.append(" { CHECK_ARRAY_ACCESS(2, SP[-1].data.i); /* BALOAD */ \n" + + b.append(" { "); + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append("CHECK_ARRAY_ACCESS(2, SP[-1].data.i); "); + } + b.append("/* BALOAD */ \n" + " SP--; SP[-1].type = CN1_TYPE_INT; \n" + " SP[-1].data.i = ((JAVA_ARRAY_BYTE*) (*(JAVA_ARRAY)SP[-1].data.o).data)[(*SP).data.i]; \n" + " }\n"); break; case Opcodes.CALOAD: - b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); /* CALOAD */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); "); + } else { + b.append(" "); + } + b.append("/* CALOAD */\n" + " SP--; SP[-1].type = CN1_TYPE_INT; \n" + " SP[-1].data.i = ((JAVA_ARRAY_CHAR*) (*(JAVA_ARRAY)SP[-1].data.o).data)[(*SP).data.i];\n"); break; case Opcodes.IALOAD: - b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); /* IALOAD */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); "); + } else { + b.append(" "); + } + b.append("/* IALOAD */\n" + " SP--; SP[-1].type = CN1_TYPE_INT; \n" + " SP[-1].data.i = ((JAVA_ARRAY_INT*) (*(JAVA_ARRAY)SP[-1].data.o).data)[(*SP).data.i];\n"); break; case Opcodes.SALOAD: - b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); \n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); \n"); + } + b.append( " SP--; SP[-1].type = CN1_TYPE_INT; \n" + " SP[-1].data.i = ((JAVA_ARRAY_SHORT*) (*(JAVA_ARRAY)SP[-1].data.o).data)[(*SP).data.i]; /* SALOAD */\n"); break; case Opcodes.LALOAD: - b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); /* LALOAD */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); "); + } else { + b.append(" "); + } + b.append("/* LALOAD */\n" + " SP--; SP[-1].type = CN1_TYPE_LONG; \n" + " SP[-1].data.l = LONG_ARRAY_LOOKUP((JAVA_ARRAY)SP[-1].data.o, (*SP).data.i);\n"); break; case Opcodes.FALOAD: - b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); /* FALOAD */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); "); + } else { + b.append(" "); + } + b.append("/* FALOAD */\n" + " SP--; SP[-1].type = CN1_TYPE_FLOAT; \n" + " SP[-1].data.f = FLOAT_ARRAY_LOOKUP((JAVA_ARRAY)SP[-1].data.o, (*SP).data.i);\n"); break; case Opcodes.DALOAD: - b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); /* DALOAD */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); "); + } else { + b.append(" "); + } + b.append("/* DALOAD */\n" + " SP--; SP[-1].type = CN1_TYPE_DOUBLE; \n" + " SP[-1].data.d = DOUBLE_ARRAY_LOOKUP((JAVA_ARRAY)SP[-1].data.o, (*SP).data.i);\n"); break; case Opcodes.AALOAD: - b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); /* AALOAD */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(2, SP[-1].data.i); "); + } else { + b.append(" "); + } + b.append("/* AALOAD */\n" + " SP--; SP[-1].type = CN1_TYPE_INVALID; \n" + " SP[-1].data.o = ((JAVA_ARRAY_OBJECT*) (*(JAVA_ARRAY)SP[-1].data.o).data)[(*SP).data.i]; \n" + " SP[-1].type = CN1_TYPE_OBJECT; \n"); break; case Opcodes.BASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); /* BASTORE */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } else { + b.append(" "); + } + b.append("/* BASTORE */\n" + " ((JAVA_ARRAY_BYTE*) (*(JAVA_ARRAY)SP[-3].data.o).data)[SP[-2].data.i] = SP[-1].data.i; SP -= 3;\n"); break; case Opcodes.CASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); /* CASTORE */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } else { + b.append(" "); + } + b.append("/* CASTORE */\n" + " ((JAVA_ARRAY_CHAR*) (*(JAVA_ARRAY)SP[-3].data.o).data)[SP[-2].data.i] = SP[-1].data.i; SP -= 3;\n\n"); break; case Opcodes.SASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); /* SASTORE */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } else { + b.append(" "); + } + b.append("/* SASTORE */\n" + " ((JAVA_ARRAY_SHORT*) (*(JAVA_ARRAY)SP[-3].data.o).data)[SP[-2].data.i] = SP[-1].data.i; SP -= 3;\n"); break; case Opcodes.IASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); /* IASTORE */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } else { + b.append(" "); + } + b.append("/* IASTORE */\n" + " ((JAVA_ARRAY_INT*) (*(JAVA_ARRAY)SP[-3].data.o).data)[SP[-2].data.i] = SP[-1].data.i; SP -= 3;\n"); break; case Opcodes.LASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); /* LASTORE */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } else { + b.append(" "); + } + b.append("/* LASTORE */\n" + " LONG_ARRAY_LOOKUP((JAVA_ARRAY)SP[-3].data.o, SP[-2].data.i) = SP[-1].data.l; SP -= 3;\n"); break; case Opcodes.FASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); /* FASTORE */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } else { + b.append(" "); + } + b.append("/* FASTORE */\n" + " FLOAT_ARRAY_LOOKUP((JAVA_ARRAY)SP[-3].data.o, SP[-2].data.i) = SP[-1].data.f; SP -= 3;\n"); break; case Opcodes.DASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); /* DASTORE */\n" + + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } else { + b.append(" "); + } + b.append("/* DASTORE */\n" + " DOUBLE_ARRAY_LOOKUP((JAVA_ARRAY)SP[-3].data.o, SP[-2].data.i) = SP[-1].data.d; SP -= 3;\n"); break; case Opcodes.AASTORE: - b.append(" CHECK_ARRAY_ACCESS(3, SP[-2].data.i); { /* BC_AASTORE */\n" + + b.append(" "); + if (shouldEmitNullAndArrayBoundsChecks()) { + b.append("CHECK_ARRAY_ACCESS(3, SP[-2].data.i); "); + } + b.append("{ /* BC_AASTORE */\n" + " JAVA_OBJECT aastoreTmp = SP[-3].data.o; \n" + " ((JAVA_ARRAY_OBJECT*) (*(JAVA_ARRAY)aastoreTmp).data)[SP[-2].data.i] = SP[-1].data.o; \n" + " SP -= 3; }\n"); @@ -535,7 +615,11 @@ public void appendInstruction(StringBuilder b, List instructions) { if(getMethod() != null && getMethod().isBarebone()) { b.append(" return SP[-1].data.i;\n"); } else { - b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); return SP[-1].data.i;\n"); + if (getMethod() != null && getMethod().useFastReturnRelease()) { + b.append(" CN1_FAST_RETURN_RELEASE(); return SP[-1].data.i;\n"); + } else { + b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); return SP[-1].data.i;\n"); + } // b.append(maxLocals); // b.append(", stack, locals); \n return SP[-1].data.i;\n"); } @@ -551,7 +635,11 @@ public void appendInstruction(StringBuilder b, List instructions) { if(getMethod() != null && getMethod().isBarebone()) { b.append(" return POP_LONG();\n"); } else { - b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_LONG();\n"); + if (getMethod() != null && getMethod().useFastReturnRelease()) { + b.append(" CN1_FAST_RETURN_RELEASE(); \n return POP_LONG();\n"); + } else { + b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_LONG();\n"); + } } } break; @@ -565,7 +653,11 @@ public void appendInstruction(StringBuilder b, List instructions) { if(getMethod() != null && getMethod().isBarebone()) { b.append(" return POP_FLOAT();\n"); } else { - b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_FLOAT();\n"); + if (getMethod() != null && getMethod().useFastReturnRelease()) { + b.append(" CN1_FAST_RETURN_RELEASE(); \n return POP_FLOAT();\n"); + } else { + b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_FLOAT();\n"); + } } } break; @@ -579,7 +671,11 @@ public void appendInstruction(StringBuilder b, List instructions) { if(getMethod() != null && getMethod().isBarebone()) { b.append(" return POP_DOUBLE();\n"); } else { - b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_DOUBLE();\n"); + if (getMethod() != null && getMethod().useFastReturnRelease()) { + b.append(" CN1_FAST_RETURN_RELEASE(); \n return POP_DOUBLE();\n"); + } else { + b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_DOUBLE();\n"); + } } } break; @@ -593,7 +689,11 @@ public void appendInstruction(StringBuilder b, List instructions) { if(getMethod() != null && getMethod().isBarebone()) { b.append(" return POP_OBJ();\n"); } else { - b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_OBJ();\n"); + if (getMethod() != null && getMethod().useFastReturnRelease()) { + b.append(" CN1_FAST_RETURN_RELEASE(); \n return POP_OBJ();\n"); + } else { + b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return POP_OBJ();\n"); + } } } break; @@ -611,7 +711,11 @@ public void appendInstruction(StringBuilder b, List instructions) { if(getMethod() != null && getMethod().isBarebone()) { b.append(" return;\n"); } else { - b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return;\n"); + if (getMethod() != null && getMethod().useFastReturnRelease()) { + b.append(" CN1_FAST_RETURN_RELEASE(); \n return;\n"); + } else { + b.append(" releaseForReturn(threadStateData, cn1LocalsBeginInThread); \n return;\n"); + } } } break; diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/LineNumber.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/LineNumber.java index 920a01f501..c434cc9f91 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/LineNumber.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/LineNumber.java @@ -39,7 +39,7 @@ public LineNumber(String sourceFile, int line) { @Override public void appendInstruction(StringBuilder b) { - if(hasInstructions) { + if(hasInstructions && (getMethod() == null || !getMethod().isDisableDebugInfo())) { b.append(" __CN1_DEBUG_INFO("); b.append(line); b.append(");\n"); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java index 6826460048..2222f93562 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java @@ -91,6 +91,7 @@ void base64BenchmarkProducesComparableResultsInParparVm() throws Exception { "cmake", "-S", distDir.toString(), "-B", buildDir.toString(), + "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_COMPILER=clang", "-DCMAKE_OBJC_COMPILER=clang" ), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java index c46fd75a62..90035a1547 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java @@ -6,6 +6,7 @@ import com.codename1.tools.translator.bytecodes.BasicInstruction; import com.codename1.tools.translator.bytecodes.Instruction; import com.codename1.tools.translator.bytecodes.Ldc; +import com.codename1.tools.translator.bytecodes.LineNumber; import com.codename1.tools.translator.bytecodes.LocalVariable; import com.codename1.tools.translator.bytecodes.MultiArray; import com.codename1.tools.translator.bytecodes.VarOp; @@ -442,6 +443,71 @@ public AnnotationVisitor visitArray(String name) { assertTrue(delegated.get(), "AnnotationVisitorWrapper should forward to the underlying visitor"); } + @Test + void disableDebugInfoFlagSkipsLineNumberEmission() { + BytecodeMethod method = new BytecodeMethod("Example", Opcodes.ACC_STATIC, "sample", "()V", null, null); + method.setDisableDebugInfo(true); + + Instruction.setHasInstructions(true); + LineNumber lineNumber = new LineNumber("Example.java", 42); + lineNumber.setMethod(method); + + StringBuilder generated = new StringBuilder(); + lineNumber.appendInstruction(generated); + assertEquals("", generated.toString(), "Disabled debug info should suppress __CN1_DEBUG_INFO emission"); + } + + @Test + void disableNullAndArrayBoundsChecksSkipsArrayCheckEmission() { + BytecodeMethod method = new BytecodeMethod("Example", Opcodes.ACC_STATIC, "sample", "()V", null, null); + method.setDisableNullAndArrayBoundsChecks(true); + + BasicInstruction instruction = new BasicInstruction(Opcodes.IALOAD, 0); + instruction.setMethod(method); + + StringBuilder generated = new StringBuilder(); + instruction.appendInstruction(generated); + assertFalse(generated.toString().contains("CHECK_ARRAY_ACCESS"), + "Disabled null and array bounds checks should suppress CHECK_ARRAY_ACCESS emission"); + } + + @Test + void disableDebugInfoSkipsLocalVariableMetadataAndVolatileLocals() { + BytecodeMethod method = new BytecodeMethod("Example", Opcodes.ACC_STATIC, "sample", "()V", null, null); + method.setDisableDebugInfo(true); + method.addLocalVariable("counter", "I", null, new Label(), new Label(), 1); + method.addDebugInfo(100); + method.addInstruction(Opcodes.RETURN); + method.setMaxes(1, 2); + + StringBuilder generated = new StringBuilder(); + method.appendMethodC(generated); + String c = generated.toString(); + assertFalse(c.contains("volatile JAVA_INT ilocals_1_"), + "Debug-disabled methods should not emit volatile locals from local variable metadata"); + assertFalse(c.contains("__CN1_DEBUG_INFO("), + "Debug-disabled methods should not emit line debug information"); + } + + @Test + void noThrowNoMonitorNoTryMethodsUseFastMethodStackMacro() { + BytecodeMethod method = new BytecodeMethod("Example", Opcodes.ACC_STATIC, "sample", "()V", null, null); + method.addInstruction(Opcodes.ICONST_0); + method.addInstruction(Opcodes.POP); + method.addInstruction(Opcodes.RETURN); + method.setMaxes(1, 4); + + StringBuilder generated = new StringBuilder(); + method.appendMethodC(generated); + String c = generated.toString(); + assertTrue(c.contains("DEFINE_METHOD_STACK_FAST_PRIMITIVE("), + "Primitive no-throw/no-monitor/no-try methods should use DEFINE_METHOD_STACK_FAST_PRIMITIVE"); + assertTrue(c.contains("if (!class__Example.initialized) __STATIC_INITIALIZER_Example(threadStateData);"), + "Static methods should emit a fast-path loaded check before static initialization"); + assertTrue(c.contains("CN1_FAST_RETURN_RELEASE();"), + "Fast stack methods should use inline fast return release"); + } + @Test void concatenatingFileOutputStreamWritesShardedOutputs() throws Exception { Path outputDir = Files.createTempDirectory("concatenating-output"); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index b0aa5bfa3b..a2deedc7be 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -89,6 +89,7 @@ void generatesRunnableHelloWorldUsingCleanTarget(CompilerHelper.CompilerConfig c "cmake", "-S", distDir.toString(), "-B", buildDir.toString(), + "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_COMPILER=clang", "-DCMAKE_OBJC_COMPILER=clang" ), distDir);