From d5d3c3f63ab97a219fd92fa5cb7c02d420a5b8df Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Fri, 10 Apr 2026 17:34:13 +0300 Subject: [PATCH 01/14] Add ParparVM method annotations to disable debug and safety checks --- .../annotations/DisableDebugInfo.java | 14 +++ ...DisableNullChecksAndArrayBoundsChecks.java | 14 +++ .../src/com/codename1/util/Base64.java | 9 ++ .../tools/translator/BytecodeMethod.java | 18 +++ .../codename1/tools/translator/Parser.java | 8 ++ .../bytecodes/ArrayLoadExpression.java | 4 +- .../bytecodes/BasicInstruction.java | 112 +++++++++++++++--- .../translator/bytecodes/LineNumber.java | 2 +- .../BytecodeInstructionIntegrationTest.java | 29 +++++ 9 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 CodenameOne/src/com/codename1/annotations/DisableDebugInfo.java create mode 100644 CodenameOne/src/com/codename1/annotations/DisableNullChecksAndArrayBoundsChecks.java 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..3141e7603e 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -19,6 +19,9 @@ 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 { @@ -89,6 +92,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; @@ -184,6 +189,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; @@ -334,6 +341,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/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 48e68ac973..073738f82b 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -108,6 +108,8 @@ public static void setDependencyGraph(MethodDependencyGraph dependencyGraph) { private String desc; private boolean eliminated; private boolean barebone; + private boolean disableDebugInfo; + private boolean disableNullAndArrayBoundsChecks; static boolean optimizerOn; @@ -122,6 +124,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; 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..3db336616a 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"); 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/BytecodeInstructionIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java index c46fd75a62..dcd16b9679 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,34 @@ 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 concatenatingFileOutputStreamWritesShardedOutputs() throws Exception { Path outputDir = Files.createTempDirectory("concatenating-output"); From 726a0b6b53580356e49bf1fe0ceb4fc455ad7635 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Fri, 10 Apr 2026 18:54:01 +0300 Subject: [PATCH 02/14] Optimize debug-disabled methods by skipping local debug metadata --- .../tools/translator/BytecodeMethod.java | 11 ++++++++++- .../BytecodeInstructionIntegrationTest.java | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 073738f82b..b118febebd 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -881,7 +881,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; @@ -1333,6 +1336,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)); } @@ -1342,6 +1348,9 @@ public void setSourceFile(String sourceFile) { } public void addDebugInfo(int line) { + if (disableDebugInfo) { + return; + } addInstruction(new LineNumber(sourceFile, line)); } 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 dcd16b9679..ddc16e403b 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 @@ -471,6 +471,24 @@ void disableNullAndArrayBoundsChecksSkipsArrayCheckEmission() { "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 concatenatingFileOutputStreamWritesShardedOutputs() throws Exception { Path outputDir = Files.createTempDirectory("concatenating-output"); From 5d788936eb8bc138a2dd77772fd74be3f64f84fd Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Fri, 10 Apr 2026 20:35:46 +0300 Subject: [PATCH 03/14] Add fast method stack path and static init call fast-check --- vm/ByteCodeTranslator/src/cn1_globals.h | 19 ++++++ .../tools/translator/BytecodeMethod.java | 68 ++++++++++++++++--- vm/ByteCodeTranslator/src/nativeMethods.m | 15 ++++ .../BytecodeInstructionIntegrationTest.java | 17 +++++ 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index b13e6c5446..76efd33d40 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.h +++ b/vm/ByteCodeTranslator/src/cn1_globals.h @@ -1129,6 +1129,7 @@ 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); +extern void initMethodStackFast(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize); // 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 +1151,24 @@ extern void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObje const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ int methodBlockOffset = threadStateData->tryBlockOffset; +#define DEFINE_METHOD_STACK_FAST(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]; \ + initMethodStackFast(threadStateData, (JAVA_OBJECT)1, stackSize, localsStackSize); \ + const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ + int methodBlockOffset = threadStateData->tryBlockOffset; + +#define DEFINE_INSTANCE_METHOD_STACK_FAST(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]; \ + initMethodStackFast(threadStateData, __cn1ThisObject, stackSize, localsStackSize); \ + const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ + int methodBlockOffset = threadStateData->tryBlockOffset; + #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 b118febebd..820521d08b 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -286,6 +286,39 @@ 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 ArrayLoadExpression + || instruction instanceof CustomIntruction) { + 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 + || (op >= Opcodes.IALOAD && op <= Opcodes.SALOAD) + || (op >= Opcodes.IASTORE && op <= Opcodes.SASTORE) + || 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; + } public BytecodeMethod(String clsName, int access, String name, String desc, String signature, String[] exceptions) { methodName = name; @@ -865,6 +898,7 @@ public void appendMethodC(StringBuilder b) { } b.append(declaration); + boolean fastMethodStackCandidate = canUseFastMethodStack(); boolean hasInstructions = true; if(optimizerOn) { @@ -901,25 +935,43 @@ public void appendMethodC(StringBuilder b) { } } + boolean useFastMethodStack = !barebone && fastMethodStackCandidate; if(!barebone) { if(staticMethod) { if(methodName.equals("__CLINIT__")) { - b.append(" DEFINE_METHOD_STACK("); + if (useFastMethodStack) { + b.append(" DEFINE_METHOD_STACK_FAST("); + } else { + b.append(" DEFINE_METHOD_STACK("); + } } else { - b.append(" __STATIC_INITIALIZER_"); + b.append(" if (!__"); b.append(clsName.replace('/', '_').replace('$', '_')); - b.append("(threadStateData);\n DEFINE_METHOD_STACK("); + b.append("_LOADED__) __STATIC_INITIALIZER_"); + b.append(clsName.replace('/', '_').replace('$', '_')); + if (useFastMethodStack) { + b.append("(threadStateData);\n DEFINE_METHOD_STACK_FAST("); + } else { + b.append("(threadStateData);\n DEFINE_METHOD_STACK("); + } } } else { - b.append(" DEFINE_INSTANCE_METHOD_STACK("); + if (useFastMethodStack) { + b.append(" DEFINE_INSTANCE_METHOD_STACK_FAST("); + } 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"); diff --git a/vm/ByteCodeTranslator/src/nativeMethods.m b/vm/ByteCodeTranslator/src/nativeMethods.m index 0773db0d7a..e22401f8f7 100644 --- a/vm/ByteCodeTranslator/src/nativeMethods.m +++ b/vm/ByteCodeTranslator/src/nativeMethods.m @@ -1569,6 +1569,21 @@ void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int threadStateData->callStackOffset++; } +void initMethodStackFast(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize) { +#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; + } + memset(&threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset], 0, sizeof(struct elementStruct) * (localsStackSize + stackSize)); + threadStateData->threadObjectStackOffset += localsStackSize + stackSize; + threadStateData->callStackOffset++; +} + void releaseForReturn(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread) { threadStateData->threadObjectStackOffset = cn1LocalsBeginInThread; threadStateData->callStackOffset--; 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 ddc16e403b..79cd55f2d9 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 @@ -489,6 +489,23 @@ void disableDebugInfoSkipsLocalVariableMetadataAndVolatileLocals() { "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("), + "No-throw/no-monitor/no-try methods should use DEFINE_METHOD_STACK_FAST"); + assertTrue(c.contains("if (!__Example_LOADED__) __STATIC_INITIALIZER_Example(threadStateData);"), + "Static methods should emit a fast-path loaded check before static initialization"); + } + @Test void concatenatingFileOutputStreamWritesShardedOutputs() throws Exception { Path outputDir = Files.createTempDirectory("concatenating-output"); From 9145cc1c25fe06f226d7156f9e461544afb63ea9 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:02:18 +0300 Subject: [PATCH 04/14] Fix static initializer fast-path guard to use class initialized flag --- .../src/com/codename1/tools/translator/BytecodeMethod.java | 4 ++-- .../tools/translator/BytecodeInstructionIntegrationTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 820521d08b..51a3b92cea 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -945,9 +945,9 @@ public void appendMethodC(StringBuilder b) { b.append(" DEFINE_METHOD_STACK("); } } else { - b.append(" if (!__"); + b.append(" if (!class__"); b.append(clsName.replace('/', '_').replace('$', '_')); - b.append("_LOADED__) __STATIC_INITIALIZER_"); + b.append(".initialized) __STATIC_INITIALIZER_"); b.append(clsName.replace('/', '_').replace('$', '_')); if (useFastMethodStack) { b.append("(threadStateData);\n DEFINE_METHOD_STACK_FAST("); 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 79cd55f2d9..c894fc3b32 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 @@ -502,7 +502,7 @@ void noThrowNoMonitorNoTryMethodsUseFastMethodStackMacro() { String c = generated.toString(); assertTrue(c.contains("DEFINE_METHOD_STACK_FAST("), "No-throw/no-monitor/no-try methods should use DEFINE_METHOD_STACK_FAST"); - assertTrue(c.contains("if (!__Example_LOADED__) __STATIC_INITIALIZER_Example(threadStateData);"), + assertTrue(c.contains("if (!class__Example.initialized) __STATIC_INITIALIZER_Example(threadStateData);"), "Static methods should emit a fast-path loaded check before static initialization"); } From 1ca030ae9f5bf423dde82ad7c01bef572353cd25 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:56:53 +0300 Subject: [PATCH 05/14] Add primitive fast-frame path and inline fast return release --- vm/ByteCodeTranslator/src/cn1_globals.h | 54 +++++++++++++-- .../tools/translator/BytecodeMethod.java | 68 ++++++++++++++++--- .../bytecodes/BasicInstruction.java | 36 ++++++++-- vm/ByteCodeTranslator/src/nativeMethods.m | 15 ---- .../BytecodeInstructionIntegrationTest.java | 6 +- 5 files changed, 143 insertions(+), 36 deletions(-) diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index 76efd33d40..056a981d44 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.h +++ b/vm/ByteCodeTranslator/src/cn1_globals.h @@ -1129,7 +1129,29 @@ 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); -extern void initMethodStackFast(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize); +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 { + int cn1Count = localsStackSize + stackSize; + struct elementStruct* cn1Slot = &threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset]; + for (int cn1Iter = 0; cn1Iter < cn1Count; cn1Iter++) { + cn1Slot[cn1Iter].type = CN1_TYPE_INVALID; + } + } + 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 @@ -1151,24 +1173,46 @@ extern void initMethodStackFast(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1This const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ int methodBlockOffset = threadStateData->tryBlockOffset; -#define DEFINE_METHOD_STACK_FAST(stackSize, localsStackSize, spPosition) \ +#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]; \ - initMethodStackFast(threadStateData, (JAVA_OBJECT)1, stackSize, localsStackSize); \ + cn1_init_method_stack_fast(threadStateData, __cn1ThisObject, stackSize, localsStackSize, JAVA_TRUE); \ const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset;\ int methodBlockOffset = threadStateData->tryBlockOffset; -#define DEFINE_INSTANCE_METHOD_STACK_FAST(stackSize, localsStackSize, spPosition) \ +#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]; \ - initMethodStackFast(threadStateData, __cn1ThisObject, stackSize, localsStackSize); \ + 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 51a3b92cea..d05501af9c 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -110,6 +110,8 @@ public static void setDependencyGraph(MethodDependencyGraph dependencyGraph) { private boolean barebone; private boolean disableDebugInfo; private boolean disableNullAndArrayBoundsChecks; + private boolean fastMethodStackInUse; + private boolean fastMethodStackPrimitiveOnly; static boolean optimizerOn; @@ -298,20 +300,22 @@ private boolean canUseFastMethodStack() { || instruction instanceof Field || instruction instanceof TypeInstruction || instruction instanceof MultiArray - || instruction instanceof ArrayLoadExpression || 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 - || (op >= Opcodes.IALOAD && op <= Opcodes.SALOAD) - || (op >= Opcodes.IASTORE && op <= Opcodes.SASTORE) - || op == Opcodes.AALOAD || op == Opcodes.AASTORE - || op == Opcodes.BALOAD || op == Opcodes.BASTORE || op == Opcodes.CALOAD || op == Opcodes.CASTORE + || (!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; } @@ -319,6 +323,39 @@ private boolean canUseFastMethodStack() { } 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; @@ -936,11 +973,18 @@ 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__")) { if (useFastMethodStack) { - b.append(" DEFINE_METHOD_STACK_FAST("); + if (usePrimitiveFastFrame) { + b.append(" DEFINE_METHOD_STACK_FAST_PRIMITIVE("); + } else { + b.append(" DEFINE_METHOD_STACK_FAST_REF("); + } } else { b.append(" DEFINE_METHOD_STACK("); } @@ -950,14 +994,22 @@ public void appendMethodC(StringBuilder b) { b.append(".initialized) __STATIC_INITIALIZER_"); b.append(clsName.replace('/', '_').replace('$', '_')); if (useFastMethodStack) { - b.append("(threadStateData);\n DEFINE_METHOD_STACK_FAST("); + 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 { if (useFastMethodStack) { - b.append(" DEFINE_INSTANCE_METHOD_STACK_FAST("); + 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("); } 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 3db336616a..06e60b08d7 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/BasicInstruction.java @@ -615,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"); } @@ -631,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; @@ -645,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; @@ -659,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; @@ -673,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; @@ -691,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/nativeMethods.m b/vm/ByteCodeTranslator/src/nativeMethods.m index e22401f8f7..0773db0d7a 100644 --- a/vm/ByteCodeTranslator/src/nativeMethods.m +++ b/vm/ByteCodeTranslator/src/nativeMethods.m @@ -1569,21 +1569,6 @@ void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int threadStateData->callStackOffset++; } -void initMethodStackFast(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize) { -#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; - } - memset(&threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset], 0, sizeof(struct elementStruct) * (localsStackSize + stackSize)); - threadStateData->threadObjectStackOffset += localsStackSize + stackSize; - threadStateData->callStackOffset++; -} - void releaseForReturn(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread) { threadStateData->threadObjectStackOffset = cn1LocalsBeginInThread; threadStateData->callStackOffset--; 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 c894fc3b32..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 @@ -500,10 +500,12 @@ void noThrowNoMonitorNoTryMethodsUseFastMethodStackMacro() { StringBuilder generated = new StringBuilder(); method.appendMethodC(generated); String c = generated.toString(); - assertTrue(c.contains("DEFINE_METHOD_STACK_FAST("), - "No-throw/no-monitor/no-try methods should use DEFINE_METHOD_STACK_FAST"); + 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 From ec9702961fc62e3867b63e3f3eedcf95b9753136 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 06:07:08 +0300 Subject: [PATCH 06/14] Add missing exception constructor declarations for fast stack init --- vm/ByteCodeTranslator/src/cn1_globals.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index 056a981d44..31eeed7abc 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); From ace255d4b29b8931f67d52e319a7443676628c89 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 07:22:55 +0300 Subject: [PATCH 07/14] Revert Base64 annotation opt-in after no benchmark gain --- CodenameOne/src/com/codename1/util/Base64.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index 3141e7603e..3950b50847 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -19,8 +19,6 @@ 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). @@ -92,8 +90,6 @@ 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; @@ -189,8 +185,6 @@ 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; @@ -341,8 +335,6 @@ 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; From 90c6f1c700f76af312a669599a433b368d5232f4 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 07:22:59 +0300 Subject: [PATCH 08/14] Refine fast primitive frame init to avoid slow per-slot loop --- vm/ByteCodeTranslator/src/cn1_globals.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index 31eeed7abc..5b3c8bfebe 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.h +++ b/vm/ByteCodeTranslator/src/cn1_globals.h @@ -1145,11 +1145,13 @@ static inline void cn1_init_method_stack_fast(CODENAME_ONE_THREAD_STATE, JAVA_OB memset(&threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset], 0, sizeof(struct elementStruct) * (localsStackSize + stackSize)); } else { - int cn1Count = localsStackSize + stackSize; - struct elementStruct* cn1Slot = &threadStateData->threadObjectStack[threadStateData->threadObjectStackOffset]; - for (int cn1Iter = 0; cn1Iter < cn1Count; cn1Iter++) { - cn1Slot[cn1Iter].type = CN1_TYPE_INVALID; - } + /* + * 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++; From 850df0fcb0daf55515c769fa7939ac0ed3addb19 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 07:23:15 +0300 Subject: [PATCH 09/14] Restore Base64 annotation-based translator optimizations --- CodenameOne/src/com/codename1/util/Base64.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index 3950b50847..b97ffd4d7f 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -19,6 +19,9 @@ 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). @@ -90,6 +93,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; @@ -185,6 +190,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; @@ -335,6 +342,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; From c9b9765e343e9e6afea876f65fe9f1410b0d5b36 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 08:57:29 +0300 Subject: [PATCH 10/14] Optimize Base64 decode map lookups for CN1 runtime --- CodenameOne/src/com/codename1/util/Base64.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index b97ffd4d7f..724c5f09f5 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -38,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) { @@ -109,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; } @@ -127,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; } @@ -154,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; } @@ -215,7 +222,7 @@ 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[] decodeMapLocal = decodeMapInt; int fullLen = len - (pad > 0 ? 4 : 0); for (int i = 0; i < fullLen; i += 4) { From 1149946651fef163545766b95fb52aa168b2e98c Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 09:51:37 +0300 Subject: [PATCH 11/14] Tighten Base64 decode locals to aid C optimizer --- .../src/com/codename1/util/Base64.java | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index 724c5f09f5..3e32164dcd 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -106,8 +106,11 @@ public static int decode(byte[] in, int len, byte[] out) { if (len == 0) { return 0; } + byte[] inLocal = in; + byte[] outLocal = out; + int[] decodeMapLocal = decodeMapInt; if ((len & 0x3) == 0) { - int fastLength = decodeNoWhitespace(in, len, out); + int fastLength = decodeNoWhitespace(inLocal, len, outLocal); if (fastLength >= 0) { return fastLength; } @@ -115,8 +118,8 @@ public static int decode(byte[] in, int len, byte[] out) { int pad = 0; int end = len; while (end > 0) { - int chr = in[end - 1] & 0xff; - if (decodeMapInt[chr] == DECODE_WHITESPACE) { + int chr = inLocal[end - 1] & 0xff; + if (decodeMapLocal[chr] == DECODE_WHITESPACE) { end--; continue; } @@ -130,11 +133,11 @@ public static int decode(byte[] in, int len, byte[] out) { int validChars = 0; for (int i = 0; i < end; i++) { - int chr = in[i] & 0xff; + int chr = inLocal[i] & 0xff; if (chr == '=') { break; } - int value = decodeMapInt[chr]; + int value = decodeMapLocal[chr]; if (value == DECODE_WHITESPACE) { continue; } @@ -149,7 +152,7 @@ public static int decode(byte[] in, int len, byte[] out) { if (outputLength <= 0) { return 0; } - if (out.length < outputLength) { + if (outLocal.length < outputLength) { throw new IllegalArgumentException("Output buffer too small for decoded data"); } int outIndex = 0; @@ -157,11 +160,11 @@ public static int decode(byte[] in, int len, byte[] out) { int quantum = 0; int quantumChars = 0; for (int i = 0; i < end; i++) { - int chr = in[i] & 0xff; + int chr = inLocal[i] & 0xff; if (chr == '=') { break; } - int bits = decodeMapInt[chr]; + int bits = decodeMapLocal[chr]; if (bits == DECODE_WHITESPACE) { continue; } @@ -171,9 +174,9 @@ public static int decode(byte[] in, int len, byte[] out) { quantum = (quantum << 6) | bits; quantumChars++; if (quantumChars == 4) { - out[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); - out[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); - out[outIndex++] = (byte) (quantum & 0x000000FF); + outLocal[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); + outLocal[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); + outLocal[outIndex++] = (byte) (quantum & 0x000000FF); quantumChars = 0; quantum = 0; } @@ -184,9 +187,9 @@ public static int decode(byte[] in, int len, byte[] out) { return -1; } quantum = quantum << (6 * pad); - out[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); + outLocal[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); if (pad == 1) { - out[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); + outLocal[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); } } @@ -203,10 +206,13 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((len & 0x3) != 0) { return -1; } + byte[] inLocal = in; + byte[] outLocal = out; + int[] decodeMapLocal = decodeMapInt; int pad = 0; - if (len > 0 && in[len - 1] == '=') { + if (len > 0 && inLocal[len - 1] == '=') { pad++; - if (len > 1 && in[len - 2] == '=') { + if (len > 1 && inLocal[len - 2] == '=') { pad++; } } @@ -218,18 +224,17 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if (outLength <= 0) { return 0; } - if (out.length < outLength) { + if (outLocal.length < outLength) { throw new IllegalArgumentException("Output buffer too small for decoded data"); } int outIndex = 0; - int[] decodeMapLocal = decodeMapInt; int fullLen = len - (pad > 0 ? 4 : 0); for (int i = 0; i < fullLen; i += 4) { - int c0 = in[i] & 0xff; - int c1 = in[i + 1] & 0xff; - int c2 = in[i + 2] & 0xff; - int c3 = in[i + 3] & 0xff; + int c0 = inLocal[i] & 0xff; + int c1 = inLocal[i + 1] & 0xff; + int c2 = inLocal[i + 2] & 0xff; + int c3 = inLocal[i + 3] & 0xff; int b0 = decodeMapLocal[c0]; int b1 = decodeMapLocal[c1]; int b2 = decodeMapLocal[c2]; @@ -238,9 +243,9 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { return -1; } int quantum = (b0 << 18) | (b1 << 12) | (b2 << 6) | b3; - out[outIndex++] = (byte) ((quantum >> 16) & 0xff); - out[outIndex++] = (byte) ((quantum >> 8) & 0xff); - out[outIndex++] = (byte) (quantum & 0xff); + outLocal[outIndex++] = (byte) ((quantum >> 16) & 0xff); + outLocal[outIndex++] = (byte) ((quantum >> 8) & 0xff); + outLocal[outIndex++] = (byte) (quantum & 0xff); } if (pad == 0) { @@ -248,26 +253,26 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { } int i = len - 4; - int c0 = in[i] & 0xff; - int c1 = in[i + 1] & 0xff; + int c0 = inLocal[i] & 0xff; + int c1 = inLocal[i + 1] & 0xff; int b0 = decodeMapLocal[c0]; int b1 = decodeMapLocal[c1]; if ((b0 | b1) < 0) { return -1; } - out[outIndex++] = (byte) ((b0 << 2) | (b1 >> 4)); + outLocal[outIndex++] = (byte) ((b0 << 2) | (b1 >> 4)); if (pad == 2) { - return (in[i + 2] == '=' && in[i + 3] == '=') ? outIndex : -1; + return (inLocal[i + 2] == '=' && inLocal[i + 3] == '=') ? outIndex : -1; } - if (in[i + 3] != '=') { + if (inLocal[i + 3] != '=') { return -1; } - int b2 = decodeMapLocal[in[i + 2] & 0xff]; + int b2 = decodeMapLocal[inLocal[i + 2] & 0xff]; if (b2 < 0) { return -1; } - out[outIndex] = (byte) ((b1 << 4) | (b2 >> 2)); + outLocal[outIndex] = (byte) ((b1 << 4) | (b2 >> 2)); return outLength; } From ea4c1b858f19631ae69b0535595b993aae02dc4b Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 12:09:07 +0300 Subject: [PATCH 12/14] Harden Base64 fast decode writes with explicit bounds guards --- CodenameOne/src/com/codename1/util/Base64.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index 3e32164dcd..bc657c7c4a 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -242,6 +242,9 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((b0 | b1 | b2 | b3) < 0) { return -1; } + if (outIndex + 2 >= outLength) { + return -1; + } int quantum = (b0 << 18) | (b1 << 12) | (b2 << 6) | b3; outLocal[outIndex++] = (byte) ((quantum >> 16) & 0xff); outLocal[outIndex++] = (byte) ((quantum >> 8) & 0xff); @@ -260,6 +263,9 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((b0 | b1) < 0) { return -1; } + if (outIndex >= outLength) { + return -1; + } outLocal[outIndex++] = (byte) ((b0 << 2) | (b1 >> 4)); if (pad == 2) { return (inLocal[i + 2] == '=' && inLocal[i + 3] == '=') ? outIndex : -1; @@ -272,6 +278,9 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if (b2 < 0) { return -1; } + if (outIndex >= outLength) { + return -1; + } outLocal[outIndex] = (byte) ((b1 << 4) | (b2 >> 2)); return outLength; } From 8a26354ad2e689f171a6e247a0c43aee59bb16bf Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 12:59:18 +0300 Subject: [PATCH 13/14] Revert decode-local tweaks and enforce Release builds in perf tests --- .../src/com/codename1/util/Base64.java | 76 ++++++++----------- .../Base64PerformanceIntegrationTest.java | 1 + .../CleanTargetIntegrationTest.java | 1 + 3 files changed, 33 insertions(+), 45 deletions(-) diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index bc657c7c4a..4e042537bd 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -106,11 +106,8 @@ public static int decode(byte[] in, int len, byte[] out) { if (len == 0) { return 0; } - byte[] inLocal = in; - byte[] outLocal = out; - int[] decodeMapLocal = decodeMapInt; if ((len & 0x3) == 0) { - int fastLength = decodeNoWhitespace(inLocal, len, outLocal); + int fastLength = decodeNoWhitespace(in, len, out); if (fastLength >= 0) { return fastLength; } @@ -118,8 +115,8 @@ public static int decode(byte[] in, int len, byte[] out) { int pad = 0; int end = len; while (end > 0) { - int chr = inLocal[end - 1] & 0xff; - if (decodeMapLocal[chr] == DECODE_WHITESPACE) { + int chr = in[end - 1] & 0xff; + if (decodeMapInt[chr] == DECODE_WHITESPACE) { end--; continue; } @@ -133,11 +130,11 @@ public static int decode(byte[] in, int len, byte[] out) { int validChars = 0; for (int i = 0; i < end; i++) { - int chr = inLocal[i] & 0xff; + int chr = in[i] & 0xff; if (chr == '=') { break; } - int value = decodeMapLocal[chr]; + int value = decodeMapInt[chr]; if (value == DECODE_WHITESPACE) { continue; } @@ -152,7 +149,7 @@ public static int decode(byte[] in, int len, byte[] out) { if (outputLength <= 0) { return 0; } - if (outLocal.length < outputLength) { + if (out.length < outputLength) { throw new IllegalArgumentException("Output buffer too small for decoded data"); } int outIndex = 0; @@ -160,11 +157,11 @@ public static int decode(byte[] in, int len, byte[] out) { int quantum = 0; int quantumChars = 0; for (int i = 0; i < end; i++) { - int chr = inLocal[i] & 0xff; + int chr = in[i] & 0xff; if (chr == '=') { break; } - int bits = decodeMapLocal[chr]; + int bits = decodeMapInt[chr]; if (bits == DECODE_WHITESPACE) { continue; } @@ -174,9 +171,9 @@ public static int decode(byte[] in, int len, byte[] out) { quantum = (quantum << 6) | bits; quantumChars++; if (quantumChars == 4) { - outLocal[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); - outLocal[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); - outLocal[outIndex++] = (byte) (quantum & 0x000000FF); + out[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); + out[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); + out[outIndex++] = (byte) (quantum & 0x000000FF); quantumChars = 0; quantum = 0; } @@ -187,9 +184,9 @@ public static int decode(byte[] in, int len, byte[] out) { return -1; } quantum = quantum << (6 * pad); - outLocal[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); + out[outIndex++] = (byte) ((quantum & 0x00FF0000) >> 16); if (pad == 1) { - outLocal[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); + out[outIndex++] = (byte) ((quantum & 0x0000FF00) >> 8); } } @@ -206,13 +203,10 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((len & 0x3) != 0) { return -1; } - byte[] inLocal = in; - byte[] outLocal = out; - int[] decodeMapLocal = decodeMapInt; int pad = 0; - if (len > 0 && inLocal[len - 1] == '=') { + if (len > 0 && in[len - 1] == '=') { pad++; - if (len > 1 && inLocal[len - 2] == '=') { + if (len > 1 && in[len - 2] == '=') { pad++; } } @@ -224,17 +218,18 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if (outLength <= 0) { return 0; } - if (outLocal.length < outLength) { + if (out.length < outLength) { throw new IllegalArgumentException("Output buffer too small for decoded data"); } int outIndex = 0; int fullLen = len - (pad > 0 ? 4 : 0); + int[] decodeMapLocal = decodeMapInt; for (int i = 0; i < fullLen; i += 4) { - int c0 = inLocal[i] & 0xff; - int c1 = inLocal[i + 1] & 0xff; - int c2 = inLocal[i + 2] & 0xff; - int c3 = inLocal[i + 3] & 0xff; + int c0 = in[i] & 0xff; + int c1 = in[i + 1] & 0xff; + int c2 = in[i + 2] & 0xff; + int c3 = in[i + 3] & 0xff; int b0 = decodeMapLocal[c0]; int b1 = decodeMapLocal[c1]; int b2 = decodeMapLocal[c2]; @@ -242,13 +237,10 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((b0 | b1 | b2 | b3) < 0) { return -1; } - if (outIndex + 2 >= outLength) { - return -1; - } int quantum = (b0 << 18) | (b1 << 12) | (b2 << 6) | b3; - outLocal[outIndex++] = (byte) ((quantum >> 16) & 0xff); - outLocal[outIndex++] = (byte) ((quantum >> 8) & 0xff); - outLocal[outIndex++] = (byte) (quantum & 0xff); + out[outIndex++] = (byte) ((quantum >> 16) & 0xff); + out[outIndex++] = (byte) ((quantum >> 8) & 0xff); + out[outIndex++] = (byte) (quantum & 0xff); } if (pad == 0) { @@ -256,32 +248,26 @@ private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { } int i = len - 4; - int c0 = inLocal[i] & 0xff; - int c1 = inLocal[i + 1] & 0xff; + int c0 = in[i] & 0xff; + int c1 = in[i + 1] & 0xff; int b0 = decodeMapLocal[c0]; int b1 = decodeMapLocal[c1]; if ((b0 | b1) < 0) { return -1; } - if (outIndex >= outLength) { - return -1; - } - outLocal[outIndex++] = (byte) ((b0 << 2) | (b1 >> 4)); + out[outIndex++] = (byte) ((b0 << 2) | (b1 >> 4)); if (pad == 2) { - return (inLocal[i + 2] == '=' && inLocal[i + 3] == '=') ? outIndex : -1; + return (in[i + 2] == '=' && in[i + 3] == '=') ? outIndex : -1; } - if (inLocal[i + 3] != '=') { + if (in[i + 3] != '=') { return -1; } - int b2 = decodeMapLocal[inLocal[i + 2] & 0xff]; + int b2 = decodeMapLocal[in[i + 2] & 0xff]; if (b2 < 0) { return -1; } - if (outIndex >= outLength) { - return -1; - } - outLocal[outIndex] = (byte) ((b1 << 4) | (b2 >> 2)); + out[outIndex] = (byte) ((b1 << 4) | (b2 >> 2)); return outLength; } 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/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); From 1f8b029bcdfeaa4eb5318e13da44c66fa6e1d7b1 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Sat, 11 Apr 2026 15:31:22 +0300 Subject: [PATCH 14/14] Document ParparVM performance hints and tradeoffs in developer guide --- docs/developer-guide/performance.asciidoc | 54 +++++++++++++++++++++++ 1 file changed, 54 insertions(+) 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: