Skip to content

Commit 3507e0e

Browse files
authored
Add LLVM integration coverage for custom bytecodes (#4227)
1 parent c02a85a commit 3507e0e

File tree

2 files changed

+148
-14
lines changed

2 files changed

+148
-14
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package com.codename1.tools.translator;
2+
import org.junit.jupiter.api.Test;
3+
4+
import javax.tools.JavaCompiler;
5+
import javax.tools.ToolProvider;
6+
import java.nio.charset.StandardCharsets;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import java.util.Arrays;
10+
import java.util.stream.Stream;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
class BytecodeInstructionIntegrationTest {
15+
16+
@Test
17+
void translatesOptimizedBytecodeToLLVMExecutable() throws Exception {
18+
Parser.cleanup();
19+
20+
Path sourceDir = Files.createTempDirectory("bytecode-integration-sources");
21+
Path classesDir = Files.createTempDirectory("bytecode-integration-classes");
22+
23+
Files.createDirectories(sourceDir.resolve("java/lang"));
24+
Files.write(sourceDir.resolve("BytecodeInstructionApp.java"), appSource().getBytes(StandardCharsets.UTF_8));
25+
Files.write(sourceDir.resolve("java/lang/Object.java"), CleanTargetIntegrationTest.javaLangObjectSource().getBytes(StandardCharsets.UTF_8));
26+
Files.write(sourceDir.resolve("java/lang/String.java"), CleanTargetIntegrationTest.javaLangStringSource().getBytes(StandardCharsets.UTF_8));
27+
Files.write(sourceDir.resolve("java/lang/Class.java"), CleanTargetIntegrationTest.javaLangClassSource().getBytes(StandardCharsets.UTF_8));
28+
Files.write(sourceDir.resolve("java/lang/Throwable.java"), CleanTargetIntegrationTest.javaLangThrowableSource().getBytes(StandardCharsets.UTF_8));
29+
Files.write(sourceDir.resolve("java/lang/Exception.java"), CleanTargetIntegrationTest.javaLangExceptionSource().getBytes(StandardCharsets.UTF_8));
30+
Files.write(sourceDir.resolve("java/lang/RuntimeException.java"), CleanTargetIntegrationTest.javaLangRuntimeExceptionSource().getBytes(StandardCharsets.UTF_8));
31+
Files.write(sourceDir.resolve("java/lang/NullPointerException.java"), CleanTargetIntegrationTest.javaLangNullPointerExceptionSource().getBytes(StandardCharsets.UTF_8));
32+
33+
Path nativeReport = sourceDir.resolve("native_report.c");
34+
Files.write(nativeReport, nativeReportSource().getBytes(StandardCharsets.UTF_8));
35+
36+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
37+
assertNotNull(compiler, "A JDK is required to compile test sources");
38+
39+
int compileResult = compiler.run(
40+
null,
41+
null,
42+
null,
43+
"-d", classesDir.toString(),
44+
sourceDir.resolve("BytecodeInstructionApp.java").toString(),
45+
sourceDir.resolve("java/lang/Object.java").toString(),
46+
sourceDir.resolve("java/lang/String.java").toString(),
47+
sourceDir.resolve("java/lang/Class.java").toString(),
48+
sourceDir.resolve("java/lang/Throwable.java").toString(),
49+
sourceDir.resolve("java/lang/Exception.java").toString(),
50+
sourceDir.resolve("java/lang/RuntimeException.java").toString(),
51+
sourceDir.resolve("java/lang/NullPointerException.java").toString()
52+
);
53+
assertEquals(0, compileResult, "BytecodeInstructionApp should compile");
54+
55+
Files.copy(nativeReport, classesDir.resolve("native_report.c"));
56+
57+
Path outputDir = Files.createTempDirectory("bytecode-integration-output");
58+
CleanTargetIntegrationTest.runTranslator(classesDir, outputDir, "CustomBytecodeApp");
59+
60+
Path distDir = outputDir.resolve("dist");
61+
Path cmakeLists = distDir.resolve("CMakeLists.txt");
62+
assertTrue(Files.exists(cmakeLists), "Translator should emit a CMake project for the optimized sample");
63+
64+
Path srcRoot = distDir.resolve("CustomBytecodeApp-src");
65+
CleanTargetIntegrationTest.patchCn1Globals(srcRoot);
66+
CleanTargetIntegrationTest.writeRuntimeStubs(srcRoot);
67+
68+
Path generatedSource = findGeneratedSource(srcRoot);
69+
String generatedCode = new String(Files.readAllBytes(generatedSource), StandardCharsets.UTF_8);
70+
assertTrue(generatedCode.contains("CustomJump */"), "Optimized comparisons should emit CustomJump code");
71+
assertTrue(generatedCode.contains("BC_IINC"), "Increment operations should translate to BC_IINC macro");
72+
assertTrue(generatedCode.contains("VarOp.assignFrom"), "Optimized stores should rely on CustomIntruction output");
73+
74+
CleanTargetIntegrationTest.replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString());
75+
76+
Path buildDir = distDir.resolve("build");
77+
Files.createDirectories(buildDir);
78+
79+
CleanTargetIntegrationTest.runCommand(Arrays.asList(
80+
"cmake",
81+
"-S", distDir.toString(),
82+
"-B", buildDir.toString(),
83+
"-DCMAKE_C_COMPILER=clang",
84+
"-DCMAKE_OBJC_COMPILER=clang"
85+
), distDir);
86+
87+
CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir);
88+
89+
Path executable = buildDir.resolve("CustomBytecodeApp");
90+
String output = CleanTargetIntegrationTest.runCommand(Arrays.asList(executable.toString()), buildDir);
91+
assertTrue(output.contains("RESULT=14"), "Compiled program should print the expected arithmetic result");
92+
}
93+
94+
private Path findGeneratedSource(Path srcRoot) throws Exception {
95+
try (Stream<Path> paths = Files.walk(srcRoot)) {
96+
return paths
97+
.filter(p -> p.getFileName().toString().startsWith("BytecodeInstructionApp") && p.getFileName().toString().endsWith(".c"))
98+
.findFirst()
99+
.orElseThrow(() -> new AssertionError("Translated source for BytecodeInstructionApp not found"));
100+
}
101+
}
102+
103+
private String appSource() {
104+
return "public class BytecodeInstructionApp {\n" +
105+
" private static native void report(int value);\n" +
106+
" private static int optimizedComputation(int a, int b) {\n" +
107+
" int counter = a;\n" +
108+
" counter++;\n" +
109+
" int branchBase = counter;\n" +
110+
" int min;\n" +
111+
" if (branchBase < b) {\n" +
112+
" min = branchBase;\n" +
113+
" } else {\n" +
114+
" min = b + 2;\n" +
115+
" }\n" +
116+
" int result = min + counter;\n" +
117+
" return result;\n" +
118+
" }\n" +
119+
" public static void main(String[] args) {\n" +
120+
" int first = optimizedComputation(1, 3);\n" +
121+
" int second = optimizedComputation(5, 2);\n" +
122+
" report(first + second);\n" +
123+
" }\n" +
124+
"}\n";
125+
}
126+
127+
private String nativeReportSource() {
128+
return "#include \"cn1_globals.h\"\n" +
129+
"#include <stdio.h>\n" +
130+
"void BytecodeInstructionApp_report___int(CODENAME_ONE_THREAD_STATE, JAVA_INT value) {\n" +
131+
" printf(\"RESULT=%d\\n\", value);\n" +
132+
"}\n";
133+
}
134+
}

vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ void generatesRunnableHelloWorldUsingCleanTarget() throws Exception {
9494
"Compiled program should print hello message, actual output was:\n" + output);
9595
}
9696

97-
private void runTranslator(Path classesDir, Path outputDir, String appName) throws Exception {
97+
static void runTranslator(Path classesDir, Path outputDir, String appName) throws Exception {
9898
Path translatorResources = Paths.get("..", "ByteCodeTranslator", "src").normalize().toAbsolutePath();
9999
URLClassLoader systemLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
100100
URL[] systemUrls = systemLoader.getURLs();
@@ -138,7 +138,7 @@ private void runTranslator(Path classesDir, Path outputDir, String appName) thro
138138
}
139139
}
140140

141-
private void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDirName) throws IOException {
141+
static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDirName) throws IOException {
142142
String content = new String(Files.readAllBytes(cmakeLists), StandardCharsets.UTF_8);
143143
String globWithObjc = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\" \"%s/*.m\")", sourceDirName, sourceDirName);
144144
String globCOnly = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\")", sourceDirName);
@@ -150,7 +150,7 @@ private void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDi
150150
Files.write(cmakeLists, replacement.getBytes(StandardCharsets.UTF_8));
151151
}
152152

153-
private String runCommand(List<String> command, Path workingDir) throws Exception {
153+
static String runCommand(List<String> command, Path workingDir) throws Exception {
154154
ProcessBuilder builder = new ProcessBuilder(command);
155155
builder.directory(workingDir.toFile());
156156
builder.redirectErrorStream(true);
@@ -164,7 +164,7 @@ private String runCommand(List<String> command, Path workingDir) throws Exceptio
164164
return output;
165165
}
166166

167-
private void patchCn1Globals(Path srcRoot) throws IOException {
167+
static void patchCn1Globals(Path srcRoot) throws IOException {
168168
Path cn1Globals = srcRoot.resolve("cn1_globals.h");
169169
String content = new String(Files.readAllBytes(cn1Globals), StandardCharsets.UTF_8);
170170
if (!content.contains("@class NSString;")) {
@@ -173,7 +173,7 @@ private void patchCn1Globals(Path srcRoot) throws IOException {
173173
}
174174
}
175175

176-
private void writeRuntimeStubs(Path srcRoot) throws IOException {
176+
static void writeRuntimeStubs(Path srcRoot) throws IOException {
177177
Path stubs = srcRoot.resolve("runtime_stubs.c");
178178
if (Files.exists(stubs)) {
179179
return;
@@ -243,7 +243,7 @@ private void writeRuntimeStubs(Path srcRoot) throws IOException {
243243
Files.write(stubs, content.getBytes(StandardCharsets.UTF_8));
244244
}
245245

246-
private String helloWorldSource() {
246+
static String helloWorldSource() {
247247
return "public class HelloWorld {\n" +
248248
" private static native void nativeHello();\n" +
249249
" public static void main(String[] args) {\n" +
@@ -252,49 +252,49 @@ private String helloWorldSource() {
252252
"}\n";
253253
}
254254

255-
private String javaLangObjectSource() {
255+
static String javaLangObjectSource() {
256256
return "package java.lang;\n" +
257257
"public class Object {\n" +
258258
"}\n";
259259
}
260260

261-
private String javaLangStringSource() {
261+
static String javaLangStringSource() {
262262
return "package java.lang;\n" +
263263
"public class String extends Object {\n" +
264264
"}\n";
265265
}
266266

267-
private String javaLangClassSource() {
267+
static String javaLangClassSource() {
268268
return "package java.lang;\n" +
269269
"public final class Class extends Object {\n" +
270270
"}\n";
271271
}
272272

273-
private String javaLangThrowableSource() {
273+
static String javaLangThrowableSource() {
274274
return "package java.lang;\n" +
275275
"public class Throwable extends Object {\n" +
276276
"}\n";
277277
}
278278

279-
private String javaLangExceptionSource() {
279+
static String javaLangExceptionSource() {
280280
return "package java.lang;\n" +
281281
"public class Exception extends Throwable {\n" +
282282
"}\n";
283283
}
284284

285-
private String javaLangRuntimeExceptionSource() {
285+
static String javaLangRuntimeExceptionSource() {
286286
return "package java.lang;\n" +
287287
"public class RuntimeException extends Exception {\n" +
288288
"}\n";
289289
}
290290

291-
private String javaLangNullPointerExceptionSource() {
291+
static String javaLangNullPointerExceptionSource() {
292292
return "package java.lang;\n" +
293293
"public class NullPointerException extends RuntimeException {\n" +
294294
"}\n";
295295
}
296296

297-
private String nativeHelloSource() {
297+
static String nativeHelloSource() {
298298
return "#include \"cn1_globals.h\"\n" +
299299
"#include <stdio.h>\n" +
300300
"void HelloWorld_nativeHello__(CODENAME_ONE_THREAD_STATE) {\n" +

0 commit comments

Comments
 (0)