From d6522984df91ffd0b2a09797236815f6e81defd8 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:46:05 +0300 Subject: [PATCH 01/14] Refine SIMD docs with real loop example and anti-pattern FAQ --- .../src/com/codename1/annotations/Simd.java | 64 ++++++ .../src/com/codename1/util/Base64.java | 6 + docs/developer-guide/performance.asciidoc | 193 ++++++++++++++++++ .../tools/translator/BytecodeMethod.java | 58 ++++++ .../codename1/tools/translator/Parser.java | 25 ++- 5 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 CodenameOne/src/com/codename1/annotations/Simd.java diff --git a/CodenameOne/src/com/codename1/annotations/Simd.java b/CodenameOne/src/com/codename1/annotations/Simd.java new file mode 100644 index 0000000000..dc67a09c36 --- /dev/null +++ b/CodenameOne/src/com/codename1/annotations/Simd.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// Helper annotations for SIMD/vectorization hints. +/// +/// These are intentionally hints only: runtimes/translators may ignore them, +/// and code should remain correct and performant without relying on them. +@SuppressWarnings("PMD.MissingStaticMethodInNonInstantiatableClass") +public final class Simd { + + /// Prohibited default constructor. + private Simd() { + throw new AssertionError("Simd should not be instantiated"); + } + + /// Marks a method as a SIMD vectorization candidate. + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + public @interface Candidate { + } + + /// Marks a method as likely containing a reduction pattern + /// (e.g. sum/min/max over an array). + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + public @interface Reduction { + } + + /// Optional preferred SIMD lane count for vectorized code generation. + /// + /// This is a hint only; translators may pick a different width based on + /// target architecture and ABI constraints. + @Retention(RetentionPolicy.CLASS) + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + public @interface WidthHint { + int value(); + } +} diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index 21a0da0f83..cf9cc8be3e 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -19,6 +19,8 @@ package com.codename1.util; +import com.codename1.annotations.Simd; + /// This class implements Base64 encoding/decoding functionality /// as specified in RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt). public abstract class Base64 { @@ -184,6 +186,8 @@ public static int decode(byte[] in, byte[] out) { return decode(in, in.length, out); } + @Simd.Candidate + @Simd.WidthHint(16) private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((len & 0x3) != 0) { return -1; @@ -334,6 +338,8 @@ public static String encodeNoNewline(byte[] in) { * @param out destination buffer * @return number of bytes written to {@code out} */ + @Simd.Candidate + @Simd.WidthHint(16) 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..107ebf3368 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -281,3 +281,196 @@ contactsDemo.addScrollListener(new ScrollListener() { ---- NOTE: Due to technical constraints we can't use a lambda in this specific case... + +==== SIMD Hint Annotations (ParparVM) + +SIMD stands for *Single Instruction, Multiple Data*. It is a CPU capability that lets one +instruction operate on multiple array elements at once (also called “lanes”). +For data-parallel code (e.g. transforms, codecs, DSP, image math), SIMD can reduce loop +overhead and improve throughput. + +Conceptually, this scalar loop: + +[source,java] +---- +for (int i = 0; i < n; i++) { + out[i] = (byte)(in[i] ^ 0x5a); +} +---- + +may be lowered by an optimizer into a vector loop plus a scalar tail: + +[source,java] +---- +int i = 0; +int vecEnd = n - (n % 16); // vector width example +for (; i < vecEnd; i += 16) { + // vectorized body over lanes [i ... i+15] +} +for (; i < n; i++) { + // scalar tail for remaining elements +} +---- + +ParparVM includes optional SIMD hint annotations that you can use to mark hot methods as vectorization candidates. + +These hints are advisory only: + +* They do **not** change correctness/semantics. +* They may be ignored on runtimes/targets that don't support a given optimization. +* You should still write clean scalar code first, then add hints where profiling shows bottlenecks. + +The annotations are in `com.codename1.annotations.Simd`: + +* `@Simd.Candidate` - marks a method as a likely SIMD candidate. +* `@Simd.Reduction` - marks methods containing reductions (e.g. sum/min/max loops). +* `@Simd.WidthHint(n)` - suggests a preferred SIMD lane width. + +===== `@Simd.Candidate` + +Use this when the method has a clear data-parallel loop where each iteration is mostly +independent (map/transform style operations). + +[source,java] +---- +@Simd.Candidate +public static int xorTransform(byte[] in, byte[] out) { + int n = Math.min(in.length, out.length); + for (int i = 0; i < n; i++) { + out[i] = (byte)(in[i] ^ 0x5a); + } + return n; +} +---- + +===== `@Simd.Reduction` + +Use this for accumulation patterns that combine many values into one result +(sum/min/max/dot product). Reductions often require special vector handling. + +[source,java] +---- +@Simd.Candidate +@Simd.Reduction +public static int sum(int[] values) { + int s = 0; + for (int i = 0; i < values.length; i++) { + s += values[i]; + } + return s; +} +---- + +===== `@Simd.WidthHint(n)` + +Use this to suggest a preferred lane count (for example 16-byte chunks for byte-oriented work). +This is only a hint; the runtime/translator may choose a different width. + +[source,java] +---- +@Simd.Candidate +@Simd.WidthHint(16) +public static int encodeChunked(byte[] in, byte[] out) { + // regular scalar implementation; translator may use hint for vector planning + int n = Math.min(in.length, out.length); + for (int i = 0; i < n; i++) { + out[i] = in[i]; + } + return n; +} +---- + +===== Using hints together + +You can combine hints when appropriate: + +[source,java] +---- +@Simd.Candidate +@Simd.Reduction +@Simd.WidthHint(8) +public static long sumLongs(long[] values) { + long s = 0L; + for (int i = 0; i < values.length; i++) { + s += values[i]; + } + return s; +} +---- + +===== What to avoid / FAQ + +SIMD hints work best when loops are regular and predictable. They are much less effective +when code has complex control flow, aliasing uncertainty, or side effects in the hot loop. + +*Avoid these patterns in the hot loop body when possible:* + +* Per-iteration object allocation. +* Method calls with unknown side effects. +* Multiple unpredictable branches in the same loop. +* Mixing unrelated work (I/O/logging/UI updates) with data-parallel math. + +*What if a method contains both SIMD-friendly and non-SIMD code?* + +That is common and fine. Prefer extracting the SIMD-friendly loop into a small helper method +and annotate that helper, while leaving orchestration/error handling in the caller: + +[source,java] +---- +public static int process(byte[] in, byte[] out) { + // setup/validation/non-SIMD control flow + int n = Math.min(in.length, out.length); + int written = processVectorFriendly(in, out, n); // SIMD-candidate helper + // non-SIMD post-processing + return written; +} + +@Simd.Candidate +@Simd.WidthHint(16) +private static int processVectorFriendly(byte[] in, byte[] out, int n) { + for (int i = 0; i < n; i++) { + out[i] = (byte)(in[i] ^ 0x5a); + } + return n; +} +---- + +*Should I annotate everything that has a loop?* + +No. Use profiling first and annotate genuine hot spots. Over-annotation adds noise and makes it +harder to tell where optimization effort should focus. + +*Do hints guarantee SIMD code generation?* + +No. They are hints, not directives. Translator/runtime safety checks and target capabilities +still decide whether vectorization is legal and profitable. + +[source,java] +---- +import com.codename1.annotations.Simd; + +public class FastOps { + @Simd.Candidate + @Simd.WidthHint(16) + public static int transform(byte[] in, byte[] out) { + int n = Math.min(in.length, out.length); + for (int i = 0; i < n; i++) { + out[i] = (byte)(in[i] ^ 0x5a); + } + return n; + } + + @Simd.Candidate + @Simd.Reduction + public static int sum(int[] values) { + int s = 0; + for (int i = 0; i < values.length; i++) { + s += values[i]; + } + return s; + } +} +---- + +Current ParparVM stages primarily consume these hints as optimizer metadata and diagnostics. +As SIMD passes mature, this same API will continue to be the forward-compatible way to provide intent. diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 48e68ac973..e34ea95e90 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -101,6 +101,9 @@ public static void setDependencyGraph(MethodDependencyGraph dependencyGraph) { private static boolean acceptStaticOnEquals; private int methodOffset; private boolean forceVirtual; + private boolean simdCandidateHint; + private boolean simdReductionHint; + private int simdWidthHint = -1; private boolean virtualOverriden; private boolean finalMethod; private boolean synchronizedMethod; @@ -1502,6 +1505,10 @@ public void setEliminated(boolean eliminated) { boolean optimize() { + if (ByteCodeTranslator.verbose && hasSimdHints()) { + System.out.println("SIMD hint metadata on " + clsName + "." + methodName + desc + ": " + getSimdHintSummary()); + } + int instructionCount = instructions.size(); // optimize away a method that only contains the void return instruction e.g. blank constructors etc. @@ -2385,6 +2392,57 @@ public String getSignature() { return desc; } + public void setSimdCandidateHint(boolean simdCandidateHint) { + this.simdCandidateHint = simdCandidateHint; + } + + public boolean isSimdCandidateHint() { + return simdCandidateHint; + } + + public void setSimdReductionHint(boolean simdReductionHint) { + this.simdReductionHint = simdReductionHint; + } + + public boolean isSimdReductionHint() { + return simdReductionHint; + } + + public void setSimdWidthHint(int simdWidthHint) { + this.simdWidthHint = simdWidthHint; + } + + public int getSimdWidthHint() { + return simdWidthHint; + } + + public boolean hasSimdHints() { + return simdCandidateHint || simdReductionHint || simdWidthHint > 0; + } + + public String getSimdHintSummary() { + StringBuilder out = new StringBuilder(); + if (simdCandidateHint) { + out.append("candidate"); + } + if (simdReductionHint) { + if (out.length() > 0) { + out.append(", "); + } + out.append("reduction"); + } + if (simdWidthHint > 0) { + if (out.length() > 0) { + out.append(", "); + } + out.append("width=").append(simdWidthHint); + } + if (out.length() == 0) { + out.append("none"); + } + return out.toString(); + } + @Override public SignatureSet nextSignature() { return null; diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java index 591aa2bff1..6262b22053 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java @@ -740,6 +740,9 @@ public void visit(int version, int access, String name, String signature, String } class MethodVisitorWrapper extends MethodVisitor { + private static final String SIMD_CANDIDATE_DESC = "Lcom/codename1/annotations/Simd$Candidate;"; + private static final String SIMD_REDUCTION_DESC = "Lcom/codename1/annotations/Simd$Reduction;"; + private static final String SIMD_WIDTH_HINT_DESC = "Lcom/codename1/annotations/Simd$WidthHint;"; private final BytecodeMethod mtd; public MethodVisitorWrapper(MethodVisitor mv, BytecodeMethod mtd) { super(Opcodes.ASM9, mv); @@ -1198,7 +1201,27 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (mv == null) return null; - return new AnnotationVisitorWrapper(super.visitAnnotation(desc, visible)); + if (SIMD_CANDIDATE_DESC.equals(desc)) { + mtd.setSimdCandidateHint(true); + } else if (SIMD_REDUCTION_DESC.equals(desc)) { + mtd.setSimdReductionHint(true); + } + + AnnotationVisitor base = super.visitAnnotation(desc, visible); + AnnotationVisitor wrapped = new AnnotationVisitorWrapper(base); + if (!SIMD_WIDTH_HINT_DESC.equals(desc)) { + return wrapped; + } + + return new AnnotationVisitor(Opcodes.ASM9, wrapped) { + @Override + public void visit(String name, Object value) { + if ("value".equals(name) && value instanceof Integer) { + mtd.setSimdWidthHint(((Integer)value).intValue()); + } + super.visit(name, value); + } + }; } @Override From f94afc9b6d0b0c9bffbfb643c866d5a1a8f56e38 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 7 Apr 2026 22:00:35 +0300 Subject: [PATCH 02/14] Add SIMD hint readiness diagnostics for verbose translator runs --- .../tools/translator/BytecodeMethod.java | 94 ++++++++++++++++++- 1 file changed, 93 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 e34ea95e90..8afb241d68 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -1506,7 +1506,7 @@ public void setEliminated(boolean eliminated) { boolean optimize() { if (ByteCodeTranslator.verbose && hasSimdHints()) { - System.out.println("SIMD hint metadata on " + clsName + "." + methodName + desc + ": " + getSimdHintSummary()); + logSimdHintStatus(); } int instructionCount = instructions.size(); @@ -2443,6 +2443,98 @@ public String getSimdHintSummary() { return out.toString(); } + private void logSimdHintStatus() { + StringBuilder reason = new StringBuilder(); + if (!simdCandidateHint) { + appendReason(reason, "missing @Simd.Candidate"); + } + if (nativeMethod || abstractMethod) { + appendReason(reason, "native/abstract method"); + } + if (synchronizedMethod) { + appendReason(reason, "synchronized method"); + } + if (hasExceptionHandlingOrMethodCalls()) { + appendReason(reason, "complex control flow or method calls"); + } + if (!hasArrayAccessOpcode()) { + appendReason(reason, "no primitive/object array access opcodes found"); + } + if (simdReductionHint && !hasReductionOpcode()) { + appendReason(reason, "marked reduction but no reduction-like arithmetic ops found"); + } + String methodId = clsName + "." + methodName + desc; + if (reason.length() == 0) { + System.out.println("SIMD hints accepted for " + methodId + ": " + getSimdHintSummary()); + } else { + System.out.println("SIMD hints noted but not currently vectorization-ready for " + methodId + + ": " + getSimdHintSummary() + " (" + reason.toString() + ")"); + } + } + + private static void appendReason(StringBuilder sb, String value) { + if (sb.length() > 0) { + sb.append("; "); + } + sb.append(value); + } + + private boolean hasArrayAccessOpcode() { + for (Instruction ins : instructions) { + switch (ins.getOpcode()) { + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + return true; + default: + break; + } + } + return false; + } + + private boolean hasReductionOpcode() { + for (Instruction ins : instructions) { + switch (ins.getOpcode()) { + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + return true; + default: + break; + } + } + return false; + } + @Override public SignatureSet nextSignature() { return null; From 2b1208592b00dfc6966d1c52c0248104541af108 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 7 Apr 2026 22:34:25 +0300 Subject: [PATCH 03/14] Validate SIMD width hints before storing translator metadata --- .../src/com/codename1/tools/translator/BytecodeMethod.java | 2 +- .../src/com/codename1/tools/translator/Parser.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 8afb241d68..bcd17f87b6 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -2409,7 +2409,7 @@ public boolean isSimdReductionHint() { } public void setSimdWidthHint(int simdWidthHint) { - this.simdWidthHint = simdWidthHint; + this.simdWidthHint = simdWidthHint > 0 ? simdWidthHint : -1; } public int getSimdWidthHint() { diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java index 6262b22053..c041bdf6b1 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java @@ -1217,7 +1217,10 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { @Override public void visit(String name, Object value) { if ("value".equals(name) && value instanceof Integer) { - mtd.setSimdWidthHint(((Integer)value).intValue()); + int widthHint = ((Integer)value).intValue(); + if (widthHint > 0) { + mtd.setSimdWidthHint(widthHint); + } } super.visit(name, value); } From cdb33b7944d6d6944f19f32afac5b42f4dddaaea Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Tue, 7 Apr 2026 22:34:30 +0300 Subject: [PATCH 04/14] Emit SIMD width hint in generated C and test it --- .../tools/translator/BytecodeMethod.java | 5 +- .../BytecodeMethodSimdHintTest.java | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index bcd17f87b6..410fab7b8d 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -848,8 +848,11 @@ public void appendMethodC(StringBuilder b) { } return; } - + b.append(declaration); + if (simdCandidateHint && simdWidthHint > 0) { + b.append(" const JAVA_INT __cn1SimdWidthHint = ").append(simdWidthHint).append(";\n"); + } boolean hasInstructions = true; if(optimizerOn) { diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java new file mode 100644 index 0000000000..b27efb0977 --- /dev/null +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -0,0 +1,62 @@ +package com.codename1.tools.translator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.Opcodes; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BytecodeMethodSimdHintTest { + + @BeforeEach + void cleanParser() { + Parser.cleanup(); + } + + @Test + void emitsSimdWidthHintConstantForCandidateMethods() { + BytecodeMethod method = new BytecodeMethod( + "com_example_SimdCarrier", + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "vectorBody", + "()V", + null, + null + ); + method.setSimdCandidateHint(true); + method.setSimdWidthHint(16); + method.setMaxes(1, 0); + method.addInstruction(Opcodes.RETURN); + + StringBuilder out = new StringBuilder(); + method.appendMethodC(out); + String generated = out.toString(); + + assertTrue(generated.contains("__cn1SimdWidthHint = 16"), + "Generated C should embed SIMD width hint constant for candidate methods"); + } + + @Test + void doesNotEmitSimdWidthHintConstantWhenHintIsMissingOrInvalid() { + BytecodeMethod method = new BytecodeMethod( + "com_example_SimdCarrier", + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "scalarBody", + "()V", + null, + null + ); + method.setSimdCandidateHint(true); + method.setSimdWidthHint(0); + method.setMaxes(1, 0); + method.addInstruction(Opcodes.RETURN); + + StringBuilder out = new StringBuilder(); + method.appendMethodC(out); + String generated = out.toString(); + + assertFalse(generated.contains("__cn1SimdWidthHint"), + "Generated C should not emit SIMD width hint constant for invalid width values"); + } +} From b58908284b68038d9d838593fd0d05cab33a4db9 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:24:53 +0300 Subject: [PATCH 05/14] Enforce SIMD annotation validity in translator --- .../tools/translator/BytecodeMethod.java | 44 ++++++++++++------- .../BytecodeMethodSimdHintTest.java | 33 +++++--------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 410fab7b8d..619fee3307 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -850,9 +850,6 @@ public void appendMethodC(StringBuilder b) { } b.append(declaration); - if (simdCandidateHint && simdWidthHint > 0) { - b.append(" const JAVA_INT __cn1SimdWidthHint = ").append(simdWidthHint).append(";\n"); - } boolean hasInstructions = true; if(optimizerOn) { @@ -1508,8 +1505,15 @@ public void setEliminated(boolean eliminated) { boolean optimize() { + if (simdCandidateHint) { + enforceValidSimdCandidate(); + } else if (simdReductionHint) { + throw new IllegalStateException("SIMD annotation validation failed for " + clsName + "." + + methodName + desc + ": @Simd.Reduction requires @Simd.Candidate"); + } + if (ByteCodeTranslator.verbose && hasSimdHints()) { - logSimdHintStatus(); + logSimdHintStatus(getSimdIneligibilityReason()); } int instructionCount = instructions.size(); @@ -2446,11 +2450,27 @@ public String getSimdHintSummary() { return out.toString(); } - private void logSimdHintStatus() { - StringBuilder reason = new StringBuilder(); - if (!simdCandidateHint) { - appendReason(reason, "missing @Simd.Candidate"); + private void logSimdHintStatus(String reason) { + String methodId = clsName + "." + methodName + desc; + if (reason == null || reason.length() == 0) { + System.out.println("SIMD hints accepted for " + methodId + ": " + getSimdHintSummary()); + } else { + System.out.println("SIMD hints noted but not currently vectorization-ready for " + methodId + + ": " + getSimdHintSummary() + " (" + reason + ")"); } + } + + private void enforceValidSimdCandidate() { + String reason = getSimdIneligibilityReason(); + if (reason.length() == 0) { + return; + } + throw new IllegalStateException("SIMD annotation validation failed for " + clsName + "." + + methodName + desc + ": " + reason); + } + + private String getSimdIneligibilityReason() { + StringBuilder reason = new StringBuilder(); if (nativeMethod || abstractMethod) { appendReason(reason, "native/abstract method"); } @@ -2466,13 +2486,7 @@ private void logSimdHintStatus() { if (simdReductionHint && !hasReductionOpcode()) { appendReason(reason, "marked reduction but no reduction-like arithmetic ops found"); } - String methodId = clsName + "." + methodName + desc; - if (reason.length() == 0) { - System.out.println("SIMD hints accepted for " + methodId + ": " + getSimdHintSummary()); - } else { - System.out.println("SIMD hints noted but not currently vectorization-ready for " + methodId - + ": " + getSimdHintSummary() + " (" + reason.toString() + ")"); - } + return reason.toString(); } private static void appendReason(StringBuilder sb, String value) { diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java index b27efb0977..c1d94f9094 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -4,8 +4,8 @@ import org.junit.jupiter.api.Test; import org.objectweb.asm.Opcodes; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; class BytecodeMethodSimdHintTest { @@ -15,11 +15,11 @@ void cleanParser() { } @Test - void emitsSimdWidthHintConstantForCandidateMethods() { + void failsForAnnotatedMethodsThatAreNotVectorizationCandidates() { BytecodeMethod method = new BytecodeMethod( "com_example_SimdCarrier", Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, - "vectorBody", + "scalarBody", "()V", null, null @@ -28,35 +28,26 @@ void emitsSimdWidthHintConstantForCandidateMethods() { method.setSimdWidthHint(16); method.setMaxes(1, 0); method.addInstruction(Opcodes.RETURN); - - StringBuilder out = new StringBuilder(); - method.appendMethodC(out); - String generated = out.toString(); - - assertTrue(generated.contains("__cn1SimdWidthHint = 16"), - "Generated C should embed SIMD width hint constant for candidate methods"); + assertThrows(IllegalStateException.class, () -> method.appendMethodC(new StringBuilder()), + "SIMD-candidate methods with no array access opcodes should fail validation"); } @Test - void doesNotEmitSimdWidthHintConstantWhenHintIsMissingOrInvalid() { + void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { BytecodeMethod method = new BytecodeMethod( "com_example_SimdCarrier", Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, - "scalarBody", + "vectorBody", "()V", null, null ); method.setSimdCandidateHint(true); - method.setSimdWidthHint(0); + method.setSimdWidthHint(16); method.setMaxes(1, 0); + method.addInstruction(Opcodes.BALOAD); method.addInstruction(Opcodes.RETURN); - - StringBuilder out = new StringBuilder(); - method.appendMethodC(out); - String generated = out.toString(); - - assertFalse(generated.contains("__cn1SimdWidthHint"), - "Generated C should not emit SIMD width hint constant for invalid width values"); + assertDoesNotThrow(() -> method.appendMethodC(new StringBuilder()), + "SIMD-candidate methods with array access opcodes should pass validation"); } } From 27777a75d66fa8512a861ceef3ff940a2474566b Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:15:15 +0300 Subject: [PATCH 06/14] Emit NEON target pragmas for SIMD-eligible methods --- .../tools/translator/BytecodeMethod.java | 29 +++++++++++++++++++ .../BytecodeMethodSimdHintTest.java | 9 +++++- 2 files changed, 37 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 619fee3307..0b05613268 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -838,6 +838,10 @@ public void appendMethodC(StringBuilder b) { if(nativeMethod) { return; } + boolean emitSimdTargetPragmas = isSimdEligibleForCodegen(); + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPush(b); + } appendCMethodPrefix(b, ""); b.append(" {\n"); if(eliminated) { @@ -846,6 +850,9 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPop(b); + } return; } @@ -999,6 +1006,9 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPop(b); + } return; } Instruction inst = instructions.get(instructions.size() - 1); @@ -1019,6 +1029,9 @@ public void appendMethodC(StringBuilder b) { b.append(" return 0;\n}\n\n"); } } + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPop(b); + } } public void appendInterfaceMethodC(StringBuilder b) { @@ -2489,6 +2502,22 @@ private String getSimdIneligibilityReason() { return reason.toString(); } + private boolean isSimdEligibleForCodegen() { + return simdCandidateHint && getSimdIneligibilityReason().length() == 0; + } + + private static void appendSimdTargetPragmaPush(StringBuilder b) { + b.append("#if defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); + b.append("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)\n"); + b.append("#endif\n"); + } + + private static void appendSimdTargetPragmaPop(StringBuilder b) { + b.append("#if defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); + b.append("#pragma clang attribute pop\n"); + b.append("#endif\n"); + } + private static void appendReason(StringBuilder sb, String value) { if (sb.length() > 0) { sb.append("; "); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java index c1d94f9094..f0908561fa 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; class BytecodeMethodSimdHintTest { @@ -47,7 +48,13 @@ void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { method.setMaxes(1, 0); method.addInstruction(Opcodes.BALOAD); method.addInstruction(Opcodes.RETURN); - assertDoesNotThrow(() -> method.appendMethodC(new StringBuilder()), + StringBuilder out = new StringBuilder(); + assertDoesNotThrow(() -> method.appendMethodC(out), "SIMD-candidate methods with array access opcodes should pass validation"); + String generated = out.toString(); + assertTrue(generated.contains("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)"), + "SIMD-eligible methods should emit NEON target pragmas"); + assertTrue(generated.contains("#pragma clang attribute pop"), + "SIMD-eligible methods should close NEON target pragma scope"); } } From e53638fcb584df82e5f7b7dc0ed4fc0416ed6ec7 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:32:04 +0300 Subject: [PATCH 07/14] Revert NEON pragma emission to fix benchmark crash --- .../tools/translator/BytecodeMethod.java | 29 ------------------- .../BytecodeMethodSimdHintTest.java | 9 +----- 2 files changed, 1 insertion(+), 37 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 0b05613268..619fee3307 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -838,10 +838,6 @@ public void appendMethodC(StringBuilder b) { if(nativeMethod) { return; } - boolean emitSimdTargetPragmas = isSimdEligibleForCodegen(); - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPush(b); - } appendCMethodPrefix(b, ""); b.append(" {\n"); if(eliminated) { @@ -850,9 +846,6 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPop(b); - } return; } @@ -1006,9 +999,6 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPop(b); - } return; } Instruction inst = instructions.get(instructions.size() - 1); @@ -1029,9 +1019,6 @@ public void appendMethodC(StringBuilder b) { b.append(" return 0;\n}\n\n"); } } - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPop(b); - } } public void appendInterfaceMethodC(StringBuilder b) { @@ -2502,22 +2489,6 @@ private String getSimdIneligibilityReason() { return reason.toString(); } - private boolean isSimdEligibleForCodegen() { - return simdCandidateHint && getSimdIneligibilityReason().length() == 0; - } - - private static void appendSimdTargetPragmaPush(StringBuilder b) { - b.append("#if defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); - b.append("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)\n"); - b.append("#endif\n"); - } - - private static void appendSimdTargetPragmaPop(StringBuilder b) { - b.append("#if defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); - b.append("#pragma clang attribute pop\n"); - b.append("#endif\n"); - } - private static void appendReason(StringBuilder sb, String value) { if (sb.length() > 0) { sb.append("; "); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java index f0908561fa..c1d94f9094 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertTrue; class BytecodeMethodSimdHintTest { @@ -48,13 +47,7 @@ void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { method.setMaxes(1, 0); method.addInstruction(Opcodes.BALOAD); method.addInstruction(Opcodes.RETURN); - StringBuilder out = new StringBuilder(); - assertDoesNotThrow(() -> method.appendMethodC(out), + assertDoesNotThrow(() -> method.appendMethodC(new StringBuilder()), "SIMD-candidate methods with array access opcodes should pass validation"); - String generated = out.toString(); - assertTrue(generated.contains("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)"), - "SIMD-eligible methods should emit NEON target pragmas"); - assertTrue(generated.contains("#pragma clang attribute pop"), - "SIMD-eligible methods should close NEON target pragma scope"); } } From 44a33ba1acec75f1d578a1f72e2feea0391c339e Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:32:10 +0300 Subject: [PATCH 08/14] Reintroduce SIMD pragmas behind opt-in guard --- docs/developer-guide/performance.asciidoc | 4 +++ .../tools/translator/BytecodeMethod.java | 29 +++++++++++++++++++ .../BytecodeMethodSimdHintTest.java | 9 +++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index 107ebf3368..30163f13c3 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -474,3 +474,7 @@ public class FastOps { Current ParparVM stages primarily consume these hints as optimizer metadata and diagnostics. As SIMD passes mature, this same API will continue to be the forward-compatible way to provide intent. + +When generating C for eligible SIMD-candidate methods, ParparVM can also emit +Clang ARM/AArch64 NEON target pragmas, but this path is disabled by default and +must be explicitly enabled with `CN1_ENABLE_SIMD_PRAGMAS` at compile time. diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 619fee3307..472909eba9 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -838,6 +838,10 @@ public void appendMethodC(StringBuilder b) { if(nativeMethod) { return; } + boolean emitSimdTargetPragmas = isSimdEligibleForCodegen(); + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPush(b); + } appendCMethodPrefix(b, ""); b.append(" {\n"); if(eliminated) { @@ -846,6 +850,9 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPop(b); + } return; } @@ -999,6 +1006,9 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPop(b); + } return; } Instruction inst = instructions.get(instructions.size() - 1); @@ -1019,6 +1029,9 @@ public void appendMethodC(StringBuilder b) { b.append(" return 0;\n}\n\n"); } } + if (emitSimdTargetPragmas) { + appendSimdTargetPragmaPop(b); + } } public void appendInterfaceMethodC(StringBuilder b) { @@ -2489,6 +2502,22 @@ private String getSimdIneligibilityReason() { return reason.toString(); } + private boolean isSimdEligibleForCodegen() { + return simdCandidateHint && getSimdIneligibilityReason().length() == 0; + } + + private static void appendSimdTargetPragmaPush(StringBuilder b) { + b.append("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); + b.append("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)\n"); + b.append("#endif\n"); + } + + private static void appendSimdTargetPragmaPop(StringBuilder b) { + b.append("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); + b.append("#pragma clang attribute pop\n"); + b.append("#endif\n"); + } + private static void appendReason(StringBuilder sb, String value) { if (sb.length() > 0) { sb.append("; "); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java index c1d94f9094..9dc0b4d65c 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; class BytecodeMethodSimdHintTest { @@ -47,7 +48,13 @@ void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { method.setMaxes(1, 0); method.addInstruction(Opcodes.BALOAD); method.addInstruction(Opcodes.RETURN); - assertDoesNotThrow(() -> method.appendMethodC(new StringBuilder()), + StringBuilder out = new StringBuilder(); + assertDoesNotThrow(() -> method.appendMethodC(out), "SIMD-candidate methods with array access opcodes should pass validation"); + String generated = out.toString(); + assertTrue(generated.contains("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__)"), + "SIMD-eligible methods should emit opt-in SIMD pragma guards"); + assertTrue(generated.contains("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)"), + "SIMD-eligible methods should include NEON targeting pragmas under the opt-in guard"); } } From cde14f2f45fba5f7ff8b670f9746254929e438b2 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:32:14 +0300 Subject: [PATCH 09/14] Add weak SIMD hook dispatch for eligible translated methods --- docs/developer-guide/performance.asciidoc | 5 ++ .../tools/translator/BytecodeMethod.java | 61 +++++++++++++++++++ .../BytecodeMethodSimdHintTest.java | 2 + 3 files changed, 68 insertions(+) diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index 30163f13c3..b9c9249d6d 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -478,3 +478,8 @@ As SIMD passes mature, this same API will continue to be the forward-compatible When generating C for eligible SIMD-candidate methods, ParparVM can also emit Clang ARM/AArch64 NEON target pragmas, but this path is disabled by default and must be explicitly enabled with `CN1_ENABLE_SIMD_PRAGMAS` at compile time. + +ParparVM now also emits a weak SIMD hook symbol for eligible methods +(`cn1_simd_`). Platform ports can provide a strong +implementation for this symbol to run a real SIMD/NEON fast-path, while keeping +the generated scalar method body as the default fallback. diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 472909eba9..3b35dfea22 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -857,6 +857,9 @@ public void appendMethodC(StringBuilder b) { } b.append(declaration); + if (isSimdEligibleForCodegen()) { + appendSimdHookCall(b); + } boolean hasInstructions = true; if(optimizerOn) { @@ -2506,6 +2509,64 @@ private boolean isSimdEligibleForCodegen() { return simdCandidateHint && getSimdIneligibilityReason().length() == 0; } + private String getSimdHookName() { + StringBuilder out = new StringBuilder(); + out.append("cn1_simd_"); + out.append(clsName); + out.append("_"); + out.append(getCMethodName()); + out.append("__"); + for (ByteCodeMethodArg args : arguments) { + args.appendCMethodExt(out); + } + if (!returnType.isVoid()) { + out.append("_R"); + returnType.appendCMethodExt(out); + } + return out.toString(); + } + + private void appendSimdHookCall(StringBuilder b) { + String simdHookName = getSimdHookName(); + b.append(" #if defined(__GNUC__) || defined(__clang__)\n"); + b.append(" extern "); + returnType.appendCSig(b); + b.append(" ").append(simdHookName).append("(CODENAME_ONE_THREAD_STATE"); + int arg = 1; + if (!staticMethod) { + b.append(", "); + new ByteCodeMethodArg(clsName, 0).appendCSig(b); + b.append(" __cn1ThisObject"); + } + for (ByteCodeMethodArg args : arguments) { + b.append(", "); + args.appendCSig(b); + b.append("__cn1Arg"); + b.append(arg++); + } + b.append(") __attribute__((weak));\n"); + b.append(" if (").append(simdHookName).append(") {\n"); + if (!returnType.isVoid()) { + b.append(" return "); + } else { + b.append(" "); + } + b.append(simdHookName).append("(threadStateData"); + arg = 1; + if (!staticMethod) { + b.append(", __cn1ThisObject"); + } + for (int i = 0; i < arguments.size(); i++) { + b.append(", __cn1Arg").append(arg++); + } + b.append(");\n"); + if (returnType.isVoid()) { + b.append(" return;\n"); + } + b.append(" }\n"); + b.append(" #endif\n"); + } + private static void appendSimdTargetPragmaPush(StringBuilder b) { b.append("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); b.append("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)\n"); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java index 9dc0b4d65c..22db16e2b7 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -52,6 +52,8 @@ void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { assertDoesNotThrow(() -> method.appendMethodC(out), "SIMD-candidate methods with array access opcodes should pass validation"); String generated = out.toString(); + assertTrue(generated.contains("cn1_simd_com_example_SimdCarrier_vectorBody__"), + "SIMD-eligible methods should emit weak SIMD hook declarations"); assertTrue(generated.contains("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__)"), "SIMD-eligible methods should emit opt-in SIMD pragma guards"); assertTrue(generated.contains("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)"), From 7b2e32fd1b6c2d34e864a950a1260fcaf318d259 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:49:40 +0300 Subject: [PATCH 10/14] Add explicit SIMD Java API with scalar fallback --- CodenameOne/src/com/codename1/simd/SIMD.java | 109 ++++++++++++++++++ docs/developer-guide/performance.asciidoc | 31 +++++ .../java/com/codename1/simd/SIMDTest.java | 27 +++++ 3 files changed, 167 insertions(+) create mode 100644 CodenameOne/src/com/codename1/simd/SIMD.java create mode 100644 maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java diff --git a/CodenameOne/src/com/codename1/simd/SIMD.java b/CodenameOne/src/com/codename1/simd/SIMD.java new file mode 100644 index 0000000000..ddc1a8133b --- /dev/null +++ b/CodenameOne/src/com/codename1/simd/SIMD.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.simd; + +/** + * Explicit SIMD helper API. + *

+ * This API is intentionally tiny and designed to be bytecode-recognition friendly for + * platform translators that can lower these calls into native SIMD instructions. + *

+ *

+ * The default Java implementation is scalar and fully functional so code remains portable. + *

+ */ +public final class SIMD { + + private SIMD() { + throw new AssertionError("SIMD should not be instantiated"); + } + + /** + * Returns true when the current runtime+translator combo can map this API to a native SIMD backend. + * The default implementation returns false. + * + * @return true if SIMD backend support is available. + */ + public static boolean isSupported() { + return false; + } + + /** + * 4-lane float vector value type. + */ + public static final class Float4 { + public final float x; + public final float y; + public final float z; + public final float w; + + public Float4(float x, float y, float z, float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + } + + public static Float4 makeFloat4(float x, float y, float z, float w) { + return new Float4(x, y, z, w); + } + + public static Float4 load(float[] values, int offset) { + return new Float4( + values[offset], + values[offset + 1], + values[offset + 2], + values[offset + 3] + ); + } + + public static void store(float[] values, int offset, Float4 value) { + values[offset] = value.x; + values[offset + 1] = value.y; + values[offset + 2] = value.z; + values[offset + 3] = value.w; + } + + public static Float4 add(Float4 a, Float4 b) { + return new Float4( + a.x + b.x, + a.y + b.y, + a.z + b.z, + a.w + b.w + ); + } + + public static Float4 mul(Float4 a, Float4 b) { + return new Float4( + a.x * b.x, + a.y * b.y, + a.z * b.z, + a.w * b.w + ); + } + + public static Float4 fma(Float4 a, Float4 b, Float4 c) { + return add(mul(a, b), c); + } +} diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index b9c9249d6d..2cdb26012e 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -483,3 +483,34 @@ ParparVM now also emits a weak SIMD hook symbol for eligible methods (`cn1_simd_`). Platform ports can provide a strong implementation for this symbol to run a real SIMD/NEON fast-path, while keeping the generated scalar method body as the default fallback. + +==== Explicit SIMD API + +For low-level control, Codename One now also includes an explicit SIMD helper API in +`com.codename1.simd.SIMD`. + +This API is intended for writing code that is straightforward to recognize by the +translator and lower directly to platform SIMD instructions. + +The default Java implementation is scalar and portable, so it runs correctly even +when SIMD lowering is not available. + +[source,java] +---- +if (SIMD.isSupported()) { + SIMD.Float4 vc = SIMD.makeFloat4(c, c, c, c); + int i = 0; + for (; i + 4 <= n; i += 4) { + SIMD.Float4 va = SIMD.load(a, i); + SIMD.Float4 vb = SIMD.load(b, i); + SIMD.store(out, i, SIMD.fma(va, vb, vc)); + } + for (; i < n; i++) { + out[i] = a[i] * b[i] + c; + } +} else { + for (int i = 0; i < n; i++) { + out[i] = a[i] * b[i] + c; + } +} +---- diff --git a/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java b/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java new file mode 100644 index 0000000000..f26ce79682 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java @@ -0,0 +1,27 @@ +package com.codename1.simd; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class SIMDTest extends UITestBase { + + @FormTest + void testSupportDefaultsToFalse() { + assertFalse(SIMD.isSupported()); + } + + @FormTest + void testFloat4OperationsFallbackScalar() { + float[] a = new float[] {1f, 2f, 3f, 4f}; + float[] b = new float[] {10f, 20f, 30f, 40f}; + float[] out = new float[4]; + SIMD.Float4 va = SIMD.load(a, 0); + SIMD.Float4 vb = SIMD.load(b, 0); + SIMD.Float4 vc = SIMD.makeFloat4(5f, 5f, 5f, 5f); + SIMD.store(out, 0, SIMD.fma(va, vb, vc)); + assertArrayEquals(new float[] {15f, 45f, 95f, 165f}, out); + } +} From 50489dcf0960fa4002227cb24e1df0677c2dbc83 Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:50:54 +0300 Subject: [PATCH 11/14] Expand explicit SIMD API and wire Base64 SIMD path --- CodenameOne/src/com/codename1/simd/SIMD.java | 107 ++++++++++++++++++ .../src/com/codename1/util/Base64.java | 81 +++++++++++++ docs/developer-guide/performance.asciidoc | 11 ++ .../java/com/codename1/simd/SIMDTest.java | 23 ++++ .../tools/translator/BytecodeMethod.java | 16 +++ .../BytecodeMethodSimdHintTest.java | 1 + 6 files changed, 239 insertions(+) diff --git a/CodenameOne/src/com/codename1/simd/SIMD.java b/CodenameOne/src/com/codename1/simd/SIMD.java index ddc1a8133b..5826403eec 100644 --- a/CodenameOne/src/com/codename1/simd/SIMD.java +++ b/CodenameOne/src/com/codename1/simd/SIMD.java @@ -106,4 +106,111 @@ public static Float4 mul(Float4 a, Float4 b) { public static Float4 fma(Float4 a, Float4 b, Float4 c) { return add(mul(a, b), c); } + + /** + * 4-lane integer vector. + */ + public static final class Int4 { + public final int x; + public final int y; + public final int z; + public final int w; + + public Int4(int x, int y, int z, int w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + } + + public static Int4 makeInt4(int x, int y, int z, int w) { + return new Int4(x, y, z, w); + } + + public static Int4 add(Int4 a, Int4 b) { + return new Int4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); + } + + public static Int4 sub(Int4 a, Int4 b) { + return new Int4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); + } + + public static Int4 and(Int4 a, Int4 b) { + return new Int4(a.x & b.x, a.y & b.y, a.z & b.z, a.w & b.w); + } + + public static Int4 or(Int4 a, Int4 b) { + return new Int4(a.x | b.x, a.y | b.y, a.z | b.z, a.w | b.w); + } + + public static Int4 shl(Int4 a, int bits) { + return new Int4(a.x << bits, a.y << bits, a.z << bits, a.w << bits); + } + + public static Int4 ushr(Int4 a, int bits) { + return new Int4(a.x >>> bits, a.y >>> bits, a.z >>> bits, a.w >>> bits); + } + + /** + * Unsigned byte 16-lane vector. + */ + public static final class U8x16 { + private final int[] lanes = new int[16]; + + private U8x16() { + } + } + + public static U8x16 loadU8(byte[] values, int offset) { + U8x16 out = new U8x16(); + for (int i = 0; i < 16; i++) { + out.lanes[i] = values[offset + i] & 0xff; + } + return out; + } + + public static int laneU8(U8x16 value, int lane) { + return value.lanes[lane] & 0xff; + } + + public static U8x16 and(U8x16 a, U8x16 b) { + U8x16 out = new U8x16(); + for (int i = 0; i < 16; i++) { + out.lanes[i] = (a.lanes[i] & b.lanes[i]) & 0xff; + } + return out; + } + + public static U8x16 or(U8x16 a, U8x16 b) { + U8x16 out = new U8x16(); + for (int i = 0; i < 16; i++) { + out.lanes[i] = (a.lanes[i] | b.lanes[i]) & 0xff; + } + return out; + } + + public static U8x16 xor(U8x16 a, U8x16 b) { + U8x16 out = new U8x16(); + for (int i = 0; i < 16; i++) { + out.lanes[i] = (a.lanes[i] ^ b.lanes[i]) & 0xff; + } + return out; + } + + public static U8x16 shl(U8x16 a, int bits) { + U8x16 out = new U8x16(); + for (int i = 0; i < 16; i++) { + out.lanes[i] = ((a.lanes[i] << bits) & 0xff); + } + return out; + } + + public static U8x16 ushr(U8x16 a, int bits) { + U8x16 out = new U8x16(); + for (int i = 0; i < 16; i++) { + out.lanes[i] = (a.lanes[i] >>> bits) & 0xff; + } + return out; + } } diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index cf9cc8be3e..9eb0864f17 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -20,6 +20,7 @@ package com.codename1.util; import com.codename1.annotations.Simd; +import com.codename1.simd.SIMD; /// This class implements Base64 encoding/decoding functionality /// as specified in RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt). @@ -27,6 +28,7 @@ public abstract class Base64 { private static final int DECODE_INVALID = -1; private static final int DECODE_WHITESPACE = -2; + private static volatile boolean explicitSimdApiEnabled; private static final byte[] map = new byte[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', @@ -349,6 +351,9 @@ public static int encodeNoNewline(byte[] in, byte[] out) { if (inputLength == 0) { return 0; } + if (explicitSimdApiEnabled && SIMD.isSupported() && inputLength >= 16) { + return encodeNoNewlineSimdApi(in, out); + } byte[] mapLocal = map; int end = inputLength - (inputLength % 3); int outIndex = 0; @@ -422,4 +427,80 @@ public static int encodeNoNewline(byte[] in, byte[] out) { } return outIndex; } + + /** + * Enables/disables the explicit SIMD API fast path. + * + * @param enabled true to enable the explicit SIMD API path + */ + public static void setExplicitSimdApiEnabled(boolean enabled) { + explicitSimdApiEnabled = enabled; + } + + public static boolean isExplicitSimdApiEnabled() { + return explicitSimdApiEnabled; + } + + private static int encodeNoNewlineSimdApi(byte[] in, byte[] out) { + int inputLength = in.length; + int outputLength = ((inputLength + 2) / 3) * 4; + byte[] mapLocal = map; + int end = inputLength - (inputLength % 3); + int outIndex = 0; + int i = 0; + for (; i + 16 <= end; i += 12) { + SIMD.U8x16 v = SIMD.loadU8(in, i); + out[outIndex++] = mapLocal[SIMD.laneU8(v, 0) >> 2]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 0) & 0x03) << 4) | (SIMD.laneU8(v, 1) >> 4)]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 1) & 0x0f) << 2) | (SIMD.laneU8(v, 2) >> 6)]; + out[outIndex++] = mapLocal[SIMD.laneU8(v, 2) & 0x3f]; + + out[outIndex++] = mapLocal[SIMD.laneU8(v, 3) >> 2]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 3) & 0x03) << 4) | (SIMD.laneU8(v, 4) >> 4)]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 4) & 0x0f) << 2) | (SIMD.laneU8(v, 5) >> 6)]; + out[outIndex++] = mapLocal[SIMD.laneU8(v, 5) & 0x3f]; + + out[outIndex++] = mapLocal[SIMD.laneU8(v, 6) >> 2]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 6) & 0x03) << 4) | (SIMD.laneU8(v, 7) >> 4)]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 7) & 0x0f) << 2) | (SIMD.laneU8(v, 8) >> 6)]; + out[outIndex++] = mapLocal[SIMD.laneU8(v, 8) & 0x3f]; + + out[outIndex++] = mapLocal[SIMD.laneU8(v, 9) >> 2]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 9) & 0x03) << 4) | (SIMD.laneU8(v, 10) >> 4)]; + out[outIndex++] = mapLocal[((SIMD.laneU8(v, 10) & 0x0f) << 2) | (SIMD.laneU8(v, 11) >> 6)]; + out[outIndex++] = mapLocal[SIMD.laneU8(v, 11) & 0x3f]; + } + for (; i < end; i += 3) { + int b0 = in[i] & 0xff; + int b1 = in[i + 1] & 0xff; + int b2 = in[i + 2] & 0xff; + + out[outIndex++] = mapLocal[b0 >> 2]; + out[outIndex++] = mapLocal[((b0 & 0x03) << 4) | (b1 >> 4)]; + out[outIndex++] = mapLocal[((b1 & 0x0f) << 2) | (b2 >> 6)]; + out[outIndex++] = mapLocal[b2 & 0x3f]; + } + switch (inputLength - end) { + case 1: { + int b0 = in[end] & 0xff; + out[outIndex++] = mapLocal[b0 >> 2]; + out[outIndex++] = mapLocal[(b0 & 0x03) << 4]; + out[outIndex++] = '='; + out[outIndex++] = '='; + break; + } + case 2: { + int b0 = in[end] & 0xff; + int b1 = in[end + 1] & 0xff; + out[outIndex++] = mapLocal[b0 >> 2]; + out[outIndex++] = mapLocal[((b0 & 0x03) << 4) | (b1 >> 4)]; + out[outIndex++] = mapLocal[(b1 & 0x0f) << 2]; + out[outIndex++] = '='; + break; + } + default: + break; + } + return outIndex; + } } diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index 2cdb26012e..921171afe1 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -495,6 +495,13 @@ translator and lower directly to platform SIMD instructions. The default Java implementation is scalar and portable, so it runs correctly even when SIMD lowering is not available. +The API currently includes: + +* `SIMD.Float4` and `SIMD.Int4` vector value types. +* `SIMD.U8x16` for unsigned-byte lane work. +* Constructors and operations such as `makeFloat4()`, `makeInt4()`, `load()`, `store()`, + `add()`, `mul()`, `fma()`, `and()`, `or()`, shifts, and lane extraction for `U8x16`. + [source,java] ---- if (SIMD.isSupported()) { @@ -514,3 +521,7 @@ if (SIMD.isSupported()) { } } ---- + +`Base64.encodeNoNewline(byte[], byte[])` also includes an explicit SIMD-API code path +that can be toggled via `Base64.setExplicitSimdApiEnabled(true)` and is activated only +when `SIMD.isSupported()` is true. diff --git a/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java b/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java index f26ce79682..1dd247912b 100644 --- a/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; class SIMDTest extends UITestBase { @@ -24,4 +25,26 @@ void testFloat4OperationsFallbackScalar() { SIMD.store(out, 0, SIMD.fma(va, vb, vc)); assertArrayEquals(new float[] {15f, 45f, 95f, 165f}, out); } + + @FormTest + void testInt4OperationsFallbackScalar() { + SIMD.Int4 a = SIMD.makeInt4(1, 2, 3, 4); + SIMD.Int4 b = SIMD.makeInt4(10, 20, 30, 40); + SIMD.Int4 c = SIMD.add(a, b); + assertEquals(11, c.x); + assertEquals(22, c.y); + assertEquals(33, c.z); + assertEquals(44, c.w); + } + + @FormTest + void testU8x16LoadAndLane() { + byte[] values = new byte[16]; + for (int i = 0; i < 16; i++) { + values[i] = (byte)(i + 1); + } + SIMD.U8x16 v = SIMD.loadU8(values, 0); + assertEquals(1, SIMD.laneU8(v, 0)); + assertEquals(16, SIMD.laneU8(v, 15)); + } } diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 3b35dfea22..107d2c41bd 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -2506,9 +2506,25 @@ private String getSimdIneligibilityReason() { } private boolean isSimdEligibleForCodegen() { + if (hasExplicitSimdApiUsage()) { + return true; + } return simdCandidateHint && getSimdIneligibilityReason().length() == 0; } + private boolean hasExplicitSimdApiUsage() { + for (Instruction ins : instructions) { + if (ins instanceof Invoke) { + String owner = ((Invoke)ins).getOwner(); + if (owner != null && (owner.equals("com/codename1/simd/SIMD") + || owner.startsWith("com/codename1/simd/SIMD$"))) { + return true; + } + } + } + return false; + } + private String getSimdHookName() { StringBuilder out = new StringBuilder(); out.append("cn1_simd_"); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java index 22db16e2b7..6fe200ca78 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -59,4 +59,5 @@ void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { assertTrue(generated.contains("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)"), "SIMD-eligible methods should include NEON targeting pragmas under the opt-in guard"); } + } From 521529ee1d574a88de68621f236a56fd875c184a Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:51:12 +0300 Subject: [PATCH 12/14] Always enable SIMD API path and fold isSupported in translator --- CodenameOne/src/com/codename1/simd/SIMD.java | 7 ++++++- .../src/com/codename1/util/Base64.java | 21 +------------------ docs/developer-guide/performance.asciidoc | 3 +-- .../java/com/codename1/simd/SIMDTest.java | 12 ++++++++++- .../tools/translator/bytecodes/Invoke.java | 7 +++++++ 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/CodenameOne/src/com/codename1/simd/SIMD.java b/CodenameOne/src/com/codename1/simd/SIMD.java index 5826403eec..5aa7b42527 100644 --- a/CodenameOne/src/com/codename1/simd/SIMD.java +++ b/CodenameOne/src/com/codename1/simd/SIMD.java @@ -45,7 +45,12 @@ private SIMD() { * @return true if SIMD backend support is available. */ public static boolean isSupported() { - return false; + String explicit = System.getProperty("cn1.parparvm"); + if ("true".equalsIgnoreCase(explicit)) { + return true; + } + String vmName = System.getProperty("java.vm.name", ""); + return vmName != null && vmName.toLowerCase().contains("parparvm"); } /** diff --git a/CodenameOne/src/com/codename1/util/Base64.java b/CodenameOne/src/com/codename1/util/Base64.java index 9eb0864f17..7bd34a9218 100644 --- a/CodenameOne/src/com/codename1/util/Base64.java +++ b/CodenameOne/src/com/codename1/util/Base64.java @@ -19,7 +19,6 @@ package com.codename1.util; -import com.codename1.annotations.Simd; import com.codename1.simd.SIMD; /// This class implements Base64 encoding/decoding functionality @@ -28,7 +27,6 @@ public abstract class Base64 { private static final int DECODE_INVALID = -1; private static final int DECODE_WHITESPACE = -2; - private static volatile boolean explicitSimdApiEnabled; private static final byte[] map = new byte[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', @@ -188,8 +186,6 @@ public static int decode(byte[] in, byte[] out) { return decode(in, in.length, out); } - @Simd.Candidate - @Simd.WidthHint(16) private static int decodeNoWhitespace(byte[] in, int len, byte[] out) { if ((len & 0x3) != 0) { return -1; @@ -340,8 +336,6 @@ public static String encodeNoNewline(byte[] in) { * @param out destination buffer * @return number of bytes written to {@code out} */ - @Simd.Candidate - @Simd.WidthHint(16) public static int encodeNoNewline(byte[] in, byte[] out) { int inputLength = in.length; int outputLength = ((inputLength + 2) / 3) * 4; @@ -351,7 +345,7 @@ public static int encodeNoNewline(byte[] in, byte[] out) { if (inputLength == 0) { return 0; } - if (explicitSimdApiEnabled && SIMD.isSupported() && inputLength >= 16) { + if (SIMD.isSupported() && inputLength >= 16) { return encodeNoNewlineSimdApi(in, out); } byte[] mapLocal = map; @@ -428,19 +422,6 @@ public static int encodeNoNewline(byte[] in, byte[] out) { return outIndex; } - /** - * Enables/disables the explicit SIMD API fast path. - * - * @param enabled true to enable the explicit SIMD API path - */ - public static void setExplicitSimdApiEnabled(boolean enabled) { - explicitSimdApiEnabled = enabled; - } - - public static boolean isExplicitSimdApiEnabled() { - return explicitSimdApiEnabled; - } - private static int encodeNoNewlineSimdApi(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 921171afe1..c0e91d42f1 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -523,5 +523,4 @@ if (SIMD.isSupported()) { ---- `Base64.encodeNoNewline(byte[], byte[])` also includes an explicit SIMD-API code path -that can be toggled via `Base64.setExplicitSimdApiEnabled(true)` and is activated only -when `SIMD.isSupported()` is true. +that is activated automatically when `SIMD.isSupported()` is true. diff --git a/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java b/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java index 1dd247912b..14afda2102 100644 --- a/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java @@ -11,7 +11,17 @@ class SIMDTest extends UITestBase { @FormTest void testSupportDefaultsToFalse() { - assertFalse(SIMD.isSupported()); + String oldValue = System.getProperty("cn1.parparvm"); + try { + System.setProperty("cn1.parparvm", "false"); + assertFalse(SIMD.isSupported()); + } finally { + if (oldValue == null) { + System.clearProperty("cn1.parparvm"); + } else { + System.setProperty("cn1.parparvm", oldValue); + } + } } @FormTest diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 5e10c18876..1fae3ceb9e 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -135,6 +135,13 @@ private String findActualOwner(ByteCodeClass bc) { @Override public void appendInstruction(StringBuilder b) { + if (opcode == Opcodes.INVOKESTATIC + && "com/codename1/simd/SIMD".equals(owner) + && "isSupported".equals(name) + && "()Z".equals(desc)) { + b.append(" PUSH_INT(1);\n"); + return; + } // special case for clone on an array which isn't a real method invocation if(name.equals("clone") && owner.indexOf('[') > -1) { b.append(" POP_MANY_AND_PUSH_OBJ(cloneArray(PEEK_OBJ(1)), 1);\n"); From d94c9c08dc5ea56d95bd18782e25d916cbb675fa Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:51:16 +0300 Subject: [PATCH 13/14] Simplify SIMD support probe and annotate translated SIMD API invokes --- CodenameOne/src/com/codename1/simd/SIMD.java | 7 +------ .../tools/translator/bytecodes/Invoke.java | 13 +++++++++++++ .../translator/BytecodeMethodSimdHintTest.java | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/CodenameOne/src/com/codename1/simd/SIMD.java b/CodenameOne/src/com/codename1/simd/SIMD.java index 5aa7b42527..5826403eec 100644 --- a/CodenameOne/src/com/codename1/simd/SIMD.java +++ b/CodenameOne/src/com/codename1/simd/SIMD.java @@ -45,12 +45,7 @@ private SIMD() { * @return true if SIMD backend support is available. */ public static boolean isSupported() { - String explicit = System.getProperty("cn1.parparvm"); - if ("true".equalsIgnoreCase(explicit)) { - return true; - } - String vmName = System.getProperty("java.vm.name", ""); - return vmName != null && vmName.toLowerCase().contains("parparvm"); + return false; } /** diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java index 1fae3ceb9e..dfbca8e9f7 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Invoke.java @@ -86,6 +86,11 @@ private String getCMethodName() { } return cMethodName; } + + private boolean isSimdApiOwner() { + return owner != null && (owner.equals("com/codename1/simd/SIMD") + || owner.startsWith("com/codename1/simd/SIMD$")); + } @Override public void addDependencies(List dependencyList) { @@ -142,6 +147,14 @@ public void appendInstruction(StringBuilder b) { b.append(" PUSH_INT(1);\n"); return; } + if (isSimdApiOwner()) { + b.append(" /* CN1_SIMD_API_INVOKE: ") + .append(owner.replace('/', '.')) + .append(".") + .append(name) + .append(desc) + .append(" */\n"); + } // special case for clone on an array which isn't a real method invocation if(name.equals("clone") && owner.indexOf('[') > -1) { b.append(" POP_MANY_AND_PUSH_OBJ(cloneArray(PEEK_OBJ(1)), 1);\n"); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java index 6fe200ca78..e8650104ce 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java @@ -1,5 +1,6 @@ package com.codename1.tools.translator; +import com.codename1.tools.translator.bytecodes.Invoke; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.objectweb.asm.Opcodes; @@ -60,4 +61,20 @@ void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { "SIMD-eligible methods should include NEON targeting pragmas under the opt-in guard"); } + @Test + void emitsSimdApiInvokeMarkersForApiCallsAndValueTypes() { + StringBuilder out = new StringBuilder(); + new Invoke(Opcodes.INVOKESTATIC, "com/codename1/simd/SIMD", "loadU8", "([BI)Lcom/codename1/simd/SIMD$U8x16;", false) + .appendInstruction(out); + new Invoke(Opcodes.INVOKESTATIC, "com/codename1/simd/SIMD", "laneU8", "(Lcom/codename1/simd/SIMD$U8x16;I)I", false) + .appendInstruction(out); + new Invoke(Opcodes.INVOKESPECIAL, "com/codename1/simd/SIMD$Int4", "", "(IIII)V", false) + .appendInstruction(out); + + String generated = out.toString(); + int markerCount = generated.split("CN1_SIMD_API_INVOKE:", -1).length - 1; + assertTrue(markerCount == 3, + "All SIMD API and SIMD value-type constructor calls should carry translation markers"); + } + } From 976d86d617098529d179d9127825e1e9d32dc19b Mon Sep 17 00:00:00 2001 From: liannacasper <67953602+liannacasper@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:33:46 +0300 Subject: [PATCH 14/14] Remove SIMD hint annotation pipeline and hook emission --- .../src/com/codename1/annotations/Simd.java | 64 ---- docs/developer-guide/performance.asciidoc | 243 ---------------- .../tools/translator/BytecodeMethod.java | 275 +----------------- .../codename1/tools/translator/Parser.java | 28 +- .../BytecodeMethodSimdHintTest.java | 80 ----- 5 files changed, 2 insertions(+), 688 deletions(-) delete mode 100644 CodenameOne/src/com/codename1/annotations/Simd.java delete mode 100644 vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java diff --git a/CodenameOne/src/com/codename1/annotations/Simd.java b/CodenameOne/src/com/codename1/annotations/Simd.java deleted file mode 100644 index dc67a09c36..0000000000 --- a/CodenameOne/src/com/codename1/annotations/Simd.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/// Helper annotations for SIMD/vectorization hints. -/// -/// These are intentionally hints only: runtimes/translators may ignore them, -/// and code should remain correct and performant without relying on them. -@SuppressWarnings("PMD.MissingStaticMethodInNonInstantiatableClass") -public final class Simd { - - /// Prohibited default constructor. - private Simd() { - throw new AssertionError("Simd should not be instantiated"); - } - - /// Marks a method as a SIMD vectorization candidate. - @Retention(RetentionPolicy.CLASS) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - public @interface Candidate { - } - - /// Marks a method as likely containing a reduction pattern - /// (e.g. sum/min/max over an array). - @Retention(RetentionPolicy.CLASS) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - public @interface Reduction { - } - - /// Optional preferred SIMD lane count for vectorized code generation. - /// - /// This is a hint only; translators may pick a different width based on - /// target architecture and ABI constraints. - @Retention(RetentionPolicy.CLASS) - @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) - public @interface WidthHint { - int value(); - } -} diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index c0e91d42f1..e787ec7e78 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -281,246 +281,3 @@ contactsDemo.addScrollListener(new ScrollListener() { ---- NOTE: Due to technical constraints we can't use a lambda in this specific case... - -==== SIMD Hint Annotations (ParparVM) - -SIMD stands for *Single Instruction, Multiple Data*. It is a CPU capability that lets one -instruction operate on multiple array elements at once (also called “lanes”). -For data-parallel code (e.g. transforms, codecs, DSP, image math), SIMD can reduce loop -overhead and improve throughput. - -Conceptually, this scalar loop: - -[source,java] ----- -for (int i = 0; i < n; i++) { - out[i] = (byte)(in[i] ^ 0x5a); -} ----- - -may be lowered by an optimizer into a vector loop plus a scalar tail: - -[source,java] ----- -int i = 0; -int vecEnd = n - (n % 16); // vector width example -for (; i < vecEnd; i += 16) { - // vectorized body over lanes [i ... i+15] -} -for (; i < n; i++) { - // scalar tail for remaining elements -} ----- - -ParparVM includes optional SIMD hint annotations that you can use to mark hot methods as vectorization candidates. - -These hints are advisory only: - -* They do **not** change correctness/semantics. -* They may be ignored on runtimes/targets that don't support a given optimization. -* You should still write clean scalar code first, then add hints where profiling shows bottlenecks. - -The annotations are in `com.codename1.annotations.Simd`: - -* `@Simd.Candidate` - marks a method as a likely SIMD candidate. -* `@Simd.Reduction` - marks methods containing reductions (e.g. sum/min/max loops). -* `@Simd.WidthHint(n)` - suggests a preferred SIMD lane width. - -===== `@Simd.Candidate` - -Use this when the method has a clear data-parallel loop where each iteration is mostly -independent (map/transform style operations). - -[source,java] ----- -@Simd.Candidate -public static int xorTransform(byte[] in, byte[] out) { - int n = Math.min(in.length, out.length); - for (int i = 0; i < n; i++) { - out[i] = (byte)(in[i] ^ 0x5a); - } - return n; -} ----- - -===== `@Simd.Reduction` - -Use this for accumulation patterns that combine many values into one result -(sum/min/max/dot product). Reductions often require special vector handling. - -[source,java] ----- -@Simd.Candidate -@Simd.Reduction -public static int sum(int[] values) { - int s = 0; - for (int i = 0; i < values.length; i++) { - s += values[i]; - } - return s; -} ----- - -===== `@Simd.WidthHint(n)` - -Use this to suggest a preferred lane count (for example 16-byte chunks for byte-oriented work). -This is only a hint; the runtime/translator may choose a different width. - -[source,java] ----- -@Simd.Candidate -@Simd.WidthHint(16) -public static int encodeChunked(byte[] in, byte[] out) { - // regular scalar implementation; translator may use hint for vector planning - int n = Math.min(in.length, out.length); - for (int i = 0; i < n; i++) { - out[i] = in[i]; - } - return n; -} ----- - -===== Using hints together - -You can combine hints when appropriate: - -[source,java] ----- -@Simd.Candidate -@Simd.Reduction -@Simd.WidthHint(8) -public static long sumLongs(long[] values) { - long s = 0L; - for (int i = 0; i < values.length; i++) { - s += values[i]; - } - return s; -} ----- - -===== What to avoid / FAQ - -SIMD hints work best when loops are regular and predictable. They are much less effective -when code has complex control flow, aliasing uncertainty, or side effects in the hot loop. - -*Avoid these patterns in the hot loop body when possible:* - -* Per-iteration object allocation. -* Method calls with unknown side effects. -* Multiple unpredictable branches in the same loop. -* Mixing unrelated work (I/O/logging/UI updates) with data-parallel math. - -*What if a method contains both SIMD-friendly and non-SIMD code?* - -That is common and fine. Prefer extracting the SIMD-friendly loop into a small helper method -and annotate that helper, while leaving orchestration/error handling in the caller: - -[source,java] ----- -public static int process(byte[] in, byte[] out) { - // setup/validation/non-SIMD control flow - int n = Math.min(in.length, out.length); - int written = processVectorFriendly(in, out, n); // SIMD-candidate helper - // non-SIMD post-processing - return written; -} - -@Simd.Candidate -@Simd.WidthHint(16) -private static int processVectorFriendly(byte[] in, byte[] out, int n) { - for (int i = 0; i < n; i++) { - out[i] = (byte)(in[i] ^ 0x5a); - } - return n; -} ----- - -*Should I annotate everything that has a loop?* - -No. Use profiling first and annotate genuine hot spots. Over-annotation adds noise and makes it -harder to tell where optimization effort should focus. - -*Do hints guarantee SIMD code generation?* - -No. They are hints, not directives. Translator/runtime safety checks and target capabilities -still decide whether vectorization is legal and profitable. - -[source,java] ----- -import com.codename1.annotations.Simd; - -public class FastOps { - @Simd.Candidate - @Simd.WidthHint(16) - public static int transform(byte[] in, byte[] out) { - int n = Math.min(in.length, out.length); - for (int i = 0; i < n; i++) { - out[i] = (byte)(in[i] ^ 0x5a); - } - return n; - } - - @Simd.Candidate - @Simd.Reduction - public static int sum(int[] values) { - int s = 0; - for (int i = 0; i < values.length; i++) { - s += values[i]; - } - return s; - } -} ----- - -Current ParparVM stages primarily consume these hints as optimizer metadata and diagnostics. -As SIMD passes mature, this same API will continue to be the forward-compatible way to provide intent. - -When generating C for eligible SIMD-candidate methods, ParparVM can also emit -Clang ARM/AArch64 NEON target pragmas, but this path is disabled by default and -must be explicitly enabled with `CN1_ENABLE_SIMD_PRAGMAS` at compile time. - -ParparVM now also emits a weak SIMD hook symbol for eligible methods -(`cn1_simd_`). Platform ports can provide a strong -implementation for this symbol to run a real SIMD/NEON fast-path, while keeping -the generated scalar method body as the default fallback. - -==== Explicit SIMD API - -For low-level control, Codename One now also includes an explicit SIMD helper API in -`com.codename1.simd.SIMD`. - -This API is intended for writing code that is straightforward to recognize by the -translator and lower directly to platform SIMD instructions. - -The default Java implementation is scalar and portable, so it runs correctly even -when SIMD lowering is not available. - -The API currently includes: - -* `SIMD.Float4` and `SIMD.Int4` vector value types. -* `SIMD.U8x16` for unsigned-byte lane work. -* Constructors and operations such as `makeFloat4()`, `makeInt4()`, `load()`, `store()`, - `add()`, `mul()`, `fma()`, `and()`, `or()`, shifts, and lane extraction for `U8x16`. - -[source,java] ----- -if (SIMD.isSupported()) { - SIMD.Float4 vc = SIMD.makeFloat4(c, c, c, c); - int i = 0; - for (; i + 4 <= n; i += 4) { - SIMD.Float4 va = SIMD.load(a, i); - SIMD.Float4 vb = SIMD.load(b, i); - SIMD.store(out, i, SIMD.fma(va, vb, vc)); - } - for (; i < n; i++) { - out[i] = a[i] * b[i] + c; - } -} else { - for (int i = 0; i < n; i++) { - out[i] = a[i] * b[i] + c; - } -} ----- - -`Base64.encodeNoNewline(byte[], byte[])` also includes an explicit SIMD-API code path -that is activated automatically when `SIMD.isSupported()` is true. diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java index 107d2c41bd..48e68ac973 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -101,9 +101,6 @@ public static void setDependencyGraph(MethodDependencyGraph dependencyGraph) { private static boolean acceptStaticOnEquals; private int methodOffset; private boolean forceVirtual; - private boolean simdCandidateHint; - private boolean simdReductionHint; - private int simdWidthHint = -1; private boolean virtualOverriden; private boolean finalMethod; private boolean synchronizedMethod; @@ -838,10 +835,6 @@ public void appendMethodC(StringBuilder b) { if(nativeMethod) { return; } - boolean emitSimdTargetPragmas = isSimdEligibleForCodegen(); - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPush(b); - } appendCMethodPrefix(b, ""); b.append(" {\n"); if(eliminated) { @@ -850,16 +843,10 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPop(b); - } return; } - + b.append(declaration); - if (isSimdEligibleForCodegen()) { - appendSimdHookCall(b); - } boolean hasInstructions = true; if(optimizerOn) { @@ -1009,9 +996,6 @@ public void appendMethodC(StringBuilder b) { } else { b.append(" return 0;\n}\n\n"); } - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPop(b); - } return; } Instruction inst = instructions.get(instructions.size() - 1); @@ -1032,9 +1016,6 @@ public void appendMethodC(StringBuilder b) { b.append(" return 0;\n}\n\n"); } } - if (emitSimdTargetPragmas) { - appendSimdTargetPragmaPop(b); - } } public void appendInterfaceMethodC(StringBuilder b) { @@ -1521,17 +1502,6 @@ public void setEliminated(boolean eliminated) { boolean optimize() { - if (simdCandidateHint) { - enforceValidSimdCandidate(); - } else if (simdReductionHint) { - throw new IllegalStateException("SIMD annotation validation failed for " + clsName + "." - + methodName + desc + ": @Simd.Reduction requires @Simd.Candidate"); - } - - if (ByteCodeTranslator.verbose && hasSimdHints()) { - logSimdHintStatus(getSimdIneligibilityReason()); - } - int instructionCount = instructions.size(); // optimize away a method that only contains the void return instruction e.g. blank constructors etc. @@ -2415,249 +2385,6 @@ public String getSignature() { return desc; } - public void setSimdCandidateHint(boolean simdCandidateHint) { - this.simdCandidateHint = simdCandidateHint; - } - - public boolean isSimdCandidateHint() { - return simdCandidateHint; - } - - public void setSimdReductionHint(boolean simdReductionHint) { - this.simdReductionHint = simdReductionHint; - } - - public boolean isSimdReductionHint() { - return simdReductionHint; - } - - public void setSimdWidthHint(int simdWidthHint) { - this.simdWidthHint = simdWidthHint > 0 ? simdWidthHint : -1; - } - - public int getSimdWidthHint() { - return simdWidthHint; - } - - public boolean hasSimdHints() { - return simdCandidateHint || simdReductionHint || simdWidthHint > 0; - } - - public String getSimdHintSummary() { - StringBuilder out = new StringBuilder(); - if (simdCandidateHint) { - out.append("candidate"); - } - if (simdReductionHint) { - if (out.length() > 0) { - out.append(", "); - } - out.append("reduction"); - } - if (simdWidthHint > 0) { - if (out.length() > 0) { - out.append(", "); - } - out.append("width=").append(simdWidthHint); - } - if (out.length() == 0) { - out.append("none"); - } - return out.toString(); - } - - private void logSimdHintStatus(String reason) { - String methodId = clsName + "." + methodName + desc; - if (reason == null || reason.length() == 0) { - System.out.println("SIMD hints accepted for " + methodId + ": " + getSimdHintSummary()); - } else { - System.out.println("SIMD hints noted but not currently vectorization-ready for " + methodId - + ": " + getSimdHintSummary() + " (" + reason + ")"); - } - } - - private void enforceValidSimdCandidate() { - String reason = getSimdIneligibilityReason(); - if (reason.length() == 0) { - return; - } - throw new IllegalStateException("SIMD annotation validation failed for " + clsName + "." - + methodName + desc + ": " + reason); - } - - private String getSimdIneligibilityReason() { - StringBuilder reason = new StringBuilder(); - if (nativeMethod || abstractMethod) { - appendReason(reason, "native/abstract method"); - } - if (synchronizedMethod) { - appendReason(reason, "synchronized method"); - } - if (hasExceptionHandlingOrMethodCalls()) { - appendReason(reason, "complex control flow or method calls"); - } - if (!hasArrayAccessOpcode()) { - appendReason(reason, "no primitive/object array access opcodes found"); - } - if (simdReductionHint && !hasReductionOpcode()) { - appendReason(reason, "marked reduction but no reduction-like arithmetic ops found"); - } - return reason.toString(); - } - - private boolean isSimdEligibleForCodegen() { - if (hasExplicitSimdApiUsage()) { - return true; - } - return simdCandidateHint && getSimdIneligibilityReason().length() == 0; - } - - private boolean hasExplicitSimdApiUsage() { - for (Instruction ins : instructions) { - if (ins instanceof Invoke) { - String owner = ((Invoke)ins).getOwner(); - if (owner != null && (owner.equals("com/codename1/simd/SIMD") - || owner.startsWith("com/codename1/simd/SIMD$"))) { - return true; - } - } - } - return false; - } - - private String getSimdHookName() { - StringBuilder out = new StringBuilder(); - out.append("cn1_simd_"); - out.append(clsName); - out.append("_"); - out.append(getCMethodName()); - out.append("__"); - for (ByteCodeMethodArg args : arguments) { - args.appendCMethodExt(out); - } - if (!returnType.isVoid()) { - out.append("_R"); - returnType.appendCMethodExt(out); - } - return out.toString(); - } - - private void appendSimdHookCall(StringBuilder b) { - String simdHookName = getSimdHookName(); - b.append(" #if defined(__GNUC__) || defined(__clang__)\n"); - b.append(" extern "); - returnType.appendCSig(b); - b.append(" ").append(simdHookName).append("(CODENAME_ONE_THREAD_STATE"); - int arg = 1; - if (!staticMethod) { - b.append(", "); - new ByteCodeMethodArg(clsName, 0).appendCSig(b); - b.append(" __cn1ThisObject"); - } - for (ByteCodeMethodArg args : arguments) { - b.append(", "); - args.appendCSig(b); - b.append("__cn1Arg"); - b.append(arg++); - } - b.append(") __attribute__((weak));\n"); - b.append(" if (").append(simdHookName).append(") {\n"); - if (!returnType.isVoid()) { - b.append(" return "); - } else { - b.append(" "); - } - b.append(simdHookName).append("(threadStateData"); - arg = 1; - if (!staticMethod) { - b.append(", __cn1ThisObject"); - } - for (int i = 0; i < arguments.size(); i++) { - b.append(", __cn1Arg").append(arg++); - } - b.append(");\n"); - if (returnType.isVoid()) { - b.append(" return;\n"); - } - b.append(" }\n"); - b.append(" #endif\n"); - } - - private static void appendSimdTargetPragmaPush(StringBuilder b) { - b.append("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); - b.append("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)\n"); - b.append("#endif\n"); - } - - private static void appendSimdTargetPragmaPop(StringBuilder b) { - b.append("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__) && (defined(__arm__) || defined(__aarch64__))\n"); - b.append("#pragma clang attribute pop\n"); - b.append("#endif\n"); - } - - private static void appendReason(StringBuilder sb, String value) { - if (sb.length() > 0) { - sb.append("; "); - } - sb.append(value); - } - - private boolean hasArrayAccessOpcode() { - for (Instruction ins : instructions) { - switch (ins.getOpcode()) { - case Opcodes.IALOAD: - case Opcodes.LALOAD: - case Opcodes.FALOAD: - case Opcodes.DALOAD: - case Opcodes.AALOAD: - case Opcodes.BALOAD: - case Opcodes.CALOAD: - case Opcodes.SALOAD: - case Opcodes.IASTORE: - case Opcodes.LASTORE: - case Opcodes.FASTORE: - case Opcodes.DASTORE: - case Opcodes.AASTORE: - case Opcodes.BASTORE: - case Opcodes.CASTORE: - case Opcodes.SASTORE: - return true; - default: - break; - } - } - return false; - } - - private boolean hasReductionOpcode() { - for (Instruction ins : instructions) { - switch (ins.getOpcode()) { - case Opcodes.IADD: - case Opcodes.LADD: - case Opcodes.FADD: - case Opcodes.DADD: - case Opcodes.ISUB: - case Opcodes.LSUB: - case Opcodes.FSUB: - case Opcodes.DSUB: - case Opcodes.IMUL: - case Opcodes.LMUL: - case Opcodes.FMUL: - case Opcodes.DMUL: - case Opcodes.IAND: - case Opcodes.LAND: - case Opcodes.IOR: - case Opcodes.LOR: - case Opcodes.IXOR: - case Opcodes.LXOR: - return true; - default: - break; - } - } - return false; - } - @Override public SignatureSet nextSignature() { return null; diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java index c041bdf6b1..591aa2bff1 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java @@ -740,9 +740,6 @@ public void visit(int version, int access, String name, String signature, String } class MethodVisitorWrapper extends MethodVisitor { - private static final String SIMD_CANDIDATE_DESC = "Lcom/codename1/annotations/Simd$Candidate;"; - private static final String SIMD_REDUCTION_DESC = "Lcom/codename1/annotations/Simd$Reduction;"; - private static final String SIMD_WIDTH_HINT_DESC = "Lcom/codename1/annotations/Simd$WidthHint;"; private final BytecodeMethod mtd; public MethodVisitorWrapper(MethodVisitor mv, BytecodeMethod mtd) { super(Opcodes.ASM9, mv); @@ -1201,30 +1198,7 @@ public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, Str @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (mv == null) return null; - if (SIMD_CANDIDATE_DESC.equals(desc)) { - mtd.setSimdCandidateHint(true); - } else if (SIMD_REDUCTION_DESC.equals(desc)) { - mtd.setSimdReductionHint(true); - } - - AnnotationVisitor base = super.visitAnnotation(desc, visible); - AnnotationVisitor wrapped = new AnnotationVisitorWrapper(base); - if (!SIMD_WIDTH_HINT_DESC.equals(desc)) { - return wrapped; - } - - return new AnnotationVisitor(Opcodes.ASM9, wrapped) { - @Override - public void visit(String name, Object value) { - if ("value".equals(name) && value instanceof Integer) { - int widthHint = ((Integer)value).intValue(); - if (widthHint > 0) { - mtd.setSimdWidthHint(widthHint); - } - } - super.visit(name, value); - } - }; + return new AnnotationVisitorWrapper(super.visitAnnotation(desc, visible)); } @Override diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java deleted file mode 100644 index e8650104ce..0000000000 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeMethodSimdHintTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.codename1.tools.translator; - -import com.codename1.tools.translator.bytecodes.Invoke; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.objectweb.asm.Opcodes; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class BytecodeMethodSimdHintTest { - - @BeforeEach - void cleanParser() { - Parser.cleanup(); - } - - @Test - void failsForAnnotatedMethodsThatAreNotVectorizationCandidates() { - BytecodeMethod method = new BytecodeMethod( - "com_example_SimdCarrier", - Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, - "scalarBody", - "()V", - null, - null - ); - method.setSimdCandidateHint(true); - method.setSimdWidthHint(16); - method.setMaxes(1, 0); - method.addInstruction(Opcodes.RETURN); - assertThrows(IllegalStateException.class, () -> method.appendMethodC(new StringBuilder()), - "SIMD-candidate methods with no array access opcodes should fail validation"); - } - - @Test - void allowsAnnotatedMethodsThatContainArrayAccessOpcodes() { - BytecodeMethod method = new BytecodeMethod( - "com_example_SimdCarrier", - Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, - "vectorBody", - "()V", - null, - null - ); - method.setSimdCandidateHint(true); - method.setSimdWidthHint(16); - method.setMaxes(1, 0); - method.addInstruction(Opcodes.BALOAD); - method.addInstruction(Opcodes.RETURN); - StringBuilder out = new StringBuilder(); - assertDoesNotThrow(() -> method.appendMethodC(out), - "SIMD-candidate methods with array access opcodes should pass validation"); - String generated = out.toString(); - assertTrue(generated.contains("cn1_simd_com_example_SimdCarrier_vectorBody__"), - "SIMD-eligible methods should emit weak SIMD hook declarations"); - assertTrue(generated.contains("#if defined(CN1_ENABLE_SIMD_PRAGMAS) && defined(__clang__)"), - "SIMD-eligible methods should emit opt-in SIMD pragma guards"); - assertTrue(generated.contains("#pragma clang attribute push(__attribute__((target(\"neon\"))), apply_to=function)"), - "SIMD-eligible methods should include NEON targeting pragmas under the opt-in guard"); - } - - @Test - void emitsSimdApiInvokeMarkersForApiCallsAndValueTypes() { - StringBuilder out = new StringBuilder(); - new Invoke(Opcodes.INVOKESTATIC, "com/codename1/simd/SIMD", "loadU8", "([BI)Lcom/codename1/simd/SIMD$U8x16;", false) - .appendInstruction(out); - new Invoke(Opcodes.INVOKESTATIC, "com/codename1/simd/SIMD", "laneU8", "(Lcom/codename1/simd/SIMD$U8x16;I)I", false) - .appendInstruction(out); - new Invoke(Opcodes.INVOKESPECIAL, "com/codename1/simd/SIMD$Int4", "", "(IIII)V", false) - .appendInstruction(out); - - String generated = out.toString(); - int markerCount = generated.split("CN1_SIMD_API_INVOKE:", -1).length - 1; - assertTrue(markerCount == 3, - "All SIMD API and SIMD value-type constructor calls should carry translation markers"); - } - -}