diff --git a/.github/workflows/base64-translation-dump.yml b/.github/workflows/base64-translation-dump.yml new file mode 100644 index 0000000000..0f4785ad6f --- /dev/null +++ b/.github/workflows/base64-translation-dump.yml @@ -0,0 +1,57 @@ +name: Base64 Translation Dump + +on: + workflow_dispatch: + pull_request: + branches: + - master + paths: + - 'vm/ByteCodeTranslator/**' + - 'vm/tests/**' + - '.github/workflows/base64-translation-dump.yml' + +permissions: + contents: read + +jobs: + base64-translation-dump: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + + steps: + - uses: actions/checkout@v4 + + - name: Set up Java 8 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '8' + + - name: Cache Maven dependencies + uses: actions/cache@v4 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-m2 + + - name: Build ByteCodeTranslator + run: mvn -B -f vm/pom.xml -pl ByteCodeTranslator -am -DskipTests=true install + + - name: Build Codename One core artifact + run: mvn -B -f maven/pom.xml -pl core -am -DunitTests=true -DskipTests=true -Dspotbugs.skip=true -Dmaven.javadoc.skip=true install + + - name: Generate Base64 translation dump + run: mvn -B -f vm/pom.xml -pl tests -am -Dcodenameone.core.version=8.0-SNAPSHOT -Dtest=Base64PerformanceIntegrationTest -Dsurefire.failIfNoSpecifiedTests=false test + + - name: Upload Base64 translation dump artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: base64-translation-dump + path: | + vm/tests/target/base64-translated-snippets.txt + vm/tests/target/surefire-reports/** + if-no-files-found: warn diff --git a/CodenameOne/src/com/codename1/simd/SIMD.java b/CodenameOne/src/com/codename1/simd/SIMD.java new file mode 100644 index 0000000000..5826403eec --- /dev/null +++ b/CodenameOne/src/com/codename1/simd/SIMD.java @@ -0,0 +1,216 @@ +/* + * 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); + } + + /** + * 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 21a0da0f83..7973a581e1 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.simd.SIMD; + /// This class implements Base64 encoding/decoding functionality /// as specified in RFC 2045 (http://www.ietf.org/rfc/rfc2045.txt). public abstract class Base64 { @@ -343,6 +345,9 @@ public static int encodeNoNewline(byte[] in, byte[] out) { if (inputLength == 0) { return 0; } + if (SIMD.isSupported() && inputLength >= 64) { + return encodeNoNewlineSimdApi(in, out); + } byte[] mapLocal = map; int end = inputLength - (inputLength % 3); int outIndex = 0; @@ -416,4 +421,79 @@ public static int encodeNoNewline(byte[] in, byte[] out) { } return outIndex; } + + private static int encodeNoNewlineSimdApi(byte[] in, byte[] out) { + int inputLength = in.length; + 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); + int b0 = SIMD.laneU8(v, 0); + int b1 = SIMD.laneU8(v, 1); + int b2 = SIMD.laneU8(v, 2); + int b3 = SIMD.laneU8(v, 3); + int b4 = SIMD.laneU8(v, 4); + int b5 = SIMD.laneU8(v, 5); + int b6 = SIMD.laneU8(v, 6); + int b7 = SIMD.laneU8(v, 7); + int b8 = SIMD.laneU8(v, 8); + int b9 = SIMD.laneU8(v, 9); + int b10 = SIMD.laneU8(v, 10); + int b11 = SIMD.laneU8(v, 11); + + 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]; + + out[outIndex++] = mapLocal[b3 >> 2]; + out[outIndex++] = mapLocal[((b3 & 0x03) << 4) | (b4 >> 4)]; + out[outIndex++] = mapLocal[((b4 & 0x0f) << 2) | (b5 >> 6)]; + out[outIndex++] = mapLocal[b5 & 0x3f]; + + out[outIndex++] = mapLocal[b6 >> 2]; + out[outIndex++] = mapLocal[((b6 & 0x03) << 4) | (b7 >> 4)]; + out[outIndex++] = mapLocal[((b7 & 0x0f) << 2) | (b8 >> 6)]; + out[outIndex++] = mapLocal[b8 & 0x3f]; + + out[outIndex++] = mapLocal[b9 >> 2]; + out[outIndex++] = mapLocal[((b9 & 0x03) << 4) | (b10 >> 4)]; + out[outIndex++] = mapLocal[((b10 & 0x0f) << 2) | (b11 >> 6)]; + out[outIndex++] = mapLocal[b11 & 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/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..14afda2102 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/simd/SIMDTest.java @@ -0,0 +1,60 @@ +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; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SIMDTest extends UITestBase { + + @FormTest + void testSupportDefaultsToFalse() { + 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 + 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); + } + + @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 48e68ac973..7c63cf3e8f 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java @@ -837,6 +837,12 @@ public void appendMethodC(StringBuilder b) { } appendCMethodPrefix(b, ""); b.append(" {\n"); + if (("com/codename1/simd/SIMD".equals(clsName) || "com_codename1_simd_SIMD".equals(clsName)) + && "isSupported".equals(methodName) + && "()Z".equals(desc)) { + b.append(" return 1;\n}\n\n"); + return; + } if(eliminated) { if(returnType.isVoid()) { b.append(" return;\n}\n\n"); diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java index 7abd68daee..d00a20a465 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/CustomInvoke.java @@ -65,6 +65,13 @@ private String getCMethodName() { } return cMethodName; } + + private boolean isSimdIsSupportedStaticCall() { + return origOpcode == Opcodes.INVOKESTATIC + && "com/codename1/simd/SIMD".equals(owner) + && "isSupported".equals(name) + && "()Z".equals(desc); + } public void setTargetObjectLiteral(String lit) { this.targetObjectLiteral = lit; @@ -147,6 +154,10 @@ public String getReturnValue() { public boolean appendExpression(StringBuilder b) { + if (isSimdIsSupportedStaticCall()) { + b.append("1"); + return true; + } // special case for clone on an array which isn't a real method invocation if(name.equals("clone") && owner.indexOf('[') > -1) { if (targetObjectLiteral != null) { @@ -251,6 +262,10 @@ public boolean appendExpression(StringBuilder b) { @Override public void appendInstruction(StringBuilder b) { + if (isSimdIsSupportedStaticCall()) { + 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) { if (targetObjectLiteral != null) { 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..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) { @@ -135,6 +140,21 @@ 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; + } + 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/pom.xml b/vm/tests/pom.xml index 15d260c8ee..b13544f86d 100644 --- a/vm/tests/pom.xml +++ b/vm/tests/pom.xml @@ -12,6 +12,10 @@ parparvm-tests ParparVM Java Tests + + 7.0.214 + + com.codename1.parparvm @@ -31,10 +35,16 @@ junit-jupiter test + + com.codenameone + codenameone-factory + ${codenameone.core.version} + test + com.codenameone codenameone-core - 7.0.214 + ${codenameone.core.version} test diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java index 6826460048..4bc7c34f39 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/Base64PerformanceIntegrationTest.java @@ -13,6 +13,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.nio.file.StandardOpenOption; +import java.util.stream.Stream; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -32,7 +34,9 @@ void base64BenchmarkProducesComparableResultsInParparVm() throws Exception { Path sourceDir = Files.createTempDirectory("base64-perf-sources"); Path classesDir = Files.createTempDirectory("base64-perf-classes"); Path javaApiDir = Files.createTempDirectory("base64-perf-javaapi"); + Path factoryJar = findClasspathJar("codenameone-factory"); Path coreJar = findClasspathJar("codenameone-core"); + assertNotNull(factoryJar, "codenameone-factory jar should be present on the test classpath"); assertNotNull(coreJar, "codenameone-core jar should be present on the test classpath"); Path source = sourceDir.resolve("Base64PerfApp.java"); @@ -55,10 +59,10 @@ void base64BenchmarkProducesComparableResultsInParparVm() throws Exception { compileArgs.add(config.targetVersion); if (CompilerHelper.useClasspath(config)) { compileArgs.add("-classpath"); - compileArgs.add(javaApiDir + System.getProperty("path.separator") + coreJar); + compileArgs.add(javaApiDir + System.getProperty("path.separator") + factoryJar + System.getProperty("path.separator") + coreJar); } else { compileArgs.add("-bootclasspath"); - compileArgs.add(javaApiDir + System.getProperty("path.separator") + coreJar); + compileArgs.add(javaApiDir + System.getProperty("path.separator") + factoryJar + System.getProperty("path.separator") + coreJar); compileArgs.add("-Xlint:-options"); } compileArgs.add("-d"); @@ -68,15 +72,18 @@ void base64BenchmarkProducesComparableResultsInParparVm() throws Exception { int compileResult = CompilerHelper.compile(config.jdkHome, compileArgs); assertEquals(0, compileResult, "Base64PerfApp should compile. " + CompilerHelper.getLastErrorLog()); - String javaOutput = runJavaMain(config, classesDir, javaApiDir, coreJar); + String javaOutput = runJavaMain(config, classesDir, javaApiDir, factoryJar, coreJar); String javaResult = extractLine(javaOutput, "RESULT="); assertTrue(javaResult.startsWith("RESULT="), "JavaSE should produce RESULT=. Output: " + javaOutput); CompilerHelper.copyDirectory(javaApiDir, classesDir); + unzipAllClasses(factoryJar, classesDir); unzipAllClasses(coreJar, classesDir); Path outputDir = Files.createTempDirectory("base64-perf-output"); CleanTargetIntegrationTest.runTranslator(classesDir, outputDir, "Base64PerfApp"); + Path translatedDump = dumpTranslatedBase64Methods(outputDir); + assertTrue(Files.exists(translatedDump), "Expected translated method dump file at " + translatedDump); Path distDir = outputDir.resolve("dist"); Path cmakeLists = distDir.resolve("CMakeLists.txt"); @@ -109,6 +116,77 @@ void base64BenchmarkProducesComparableResultsInParparVm() throws Exception { "ParparVM output should include DECODE_MS timing. Output: " + vmOutput); } + private Path dumpTranslatedBase64Methods(Path outputDir) throws Exception { + Path distDir = outputDir.resolve("dist"); + Path dumpFile = Paths.get("target", "base64-translated-snippets.txt"); + Files.createDirectories(dumpFile.getParent()); + Files.write(dumpFile, + Arrays.asList("Base64 translation dump from " + distDir.toAbsolutePath(), ""), + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + List symbols = Arrays.asList( + "com_codename1_util_Base64_encodeNoNewline___byte_1ARRAY_byte_1ARRAY_R_int", + "com_codename1_util_Base64_encodeNoNewlineSimdApi___byte_1ARRAY_byte_1ARRAY_R_int", + "com_codename1_simd_SIMD_isSupported___R_boolean" + ); + for (String symbol : symbols) { + String snippet = extractMethodSnippet(distDir, symbol, 220); + System.out.println("\n==== TRANSLATED METHOD SNIPPET: " + symbol + " ===="); + List outLines = new ArrayList(); + outLines.add("==== TRANSLATED METHOD SNIPPET: " + symbol + " ===="); + if (snippet.isEmpty()) { + System.out.println("(not found)"); + outLines.add("(not found)"); + } else { + System.out.println(snippet); + outLines.add(snippet); + } + outLines.add(""); + Files.write(dumpFile, + outLines, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } + System.out.println("Saved translated method dump to " + dumpFile.toAbsolutePath()); + return dumpFile; + } + + private String extractMethodSnippet(Path rootDir, String symbol, int maxLines) throws Exception { + try (Stream files = Files.walk(rootDir)) { + List candidates = files + .filter(Files::isRegularFile) + .filter(p -> p.getFileName().toString().endsWith(".c")) + .sorted() + .collect(Collectors.toList()); + String methodPattern = symbol + "("; + for (Path file : candidates) { + List lines = Files.readAllLines(file, StandardCharsets.UTF_8); + for (int i = 0; i < lines.size(); i++) { + if (!lines.get(i).contains(methodPattern)) { + continue; + } + int start = Math.max(0, i - 3); + int end = Math.min(lines.size(), i + maxLines); + return lines.subList(start, end).stream().collect(Collectors.joining("\n")); + } + } + for (Path file : candidates) { + List lines = Files.readAllLines(file, StandardCharsets.UTF_8); + for (int i = 0; i < lines.size(); i++) { + if (!lines.get(i).contains(symbol)) { + continue; + } + int start = Math.max(0, i - 3); + int end = Math.min(lines.size(), i + maxLines); + return lines.subList(start, end).stream().collect(Collectors.joining("\n")); + } + } + } + return ""; + } + private String loadAppSource() throws Exception { java.io.InputStream in = Base64PerformanceIntegrationTest.class.getResourceAsStream("/com/codename1/tools/translator/Base64PerfApp.java"); assertNotNull(in, "Base64PerfApp.java test resource should exist"); @@ -117,7 +195,7 @@ private String loadAppSource() throws Exception { } } - private String runJavaMain(CompilerHelper.CompilerConfig config, Path classesDir, Path javaApiDir, Path coreJar) throws Exception { + private String runJavaMain(CompilerHelper.CompilerConfig config, Path classesDir, Path javaApiDir, Path factoryJar, Path coreJar) throws Exception { String javaExe = config.jdkHome.resolve("bin").resolve("java").toString(); if (System.getProperty("os.name").toLowerCase().contains("win")) { javaExe += ".exe"; @@ -126,7 +204,7 @@ private String runJavaMain(CompilerHelper.CompilerConfig config, Path classesDir ProcessBuilder pb = new ProcessBuilder( javaExe, "-cp", - classesDir + System.getProperty("path.separator") + javaApiDir + System.getProperty("path.separator") + coreJar, + classesDir + System.getProperty("path.separator") + javaApiDir + System.getProperty("path.separator") + factoryJar + System.getProperty("path.separator") + coreJar, "Base64PerfApp" ); pb.redirectErrorStream(true);