Skip to content

Commit d2e9b86

Browse files
authored
Add integration coverage for Invoke and Ldc bytecodes (#4234)
1 parent 5c357e5 commit d2e9b86

File tree

1 file changed

+212
-17
lines changed

1 file changed

+212
-17
lines changed

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

Lines changed: 212 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,33 +40,24 @@ void translatesOptimizedBytecodeToLLVMExecutable() throws Exception {
4040

4141
Files.createDirectories(sourceDir.resolve("java/lang"));
4242
Files.write(sourceDir.resolve("BytecodeInstructionApp.java"), appSource().getBytes(StandardCharsets.UTF_8));
43-
Files.write(sourceDir.resolve("java/lang/Object.java"), CleanTargetIntegrationTest.javaLangObjectSource().getBytes(StandardCharsets.UTF_8));
44-
Files.write(sourceDir.resolve("java/lang/String.java"), CleanTargetIntegrationTest.javaLangStringSource().getBytes(StandardCharsets.UTF_8));
45-
Files.write(sourceDir.resolve("java/lang/Class.java"), CleanTargetIntegrationTest.javaLangClassSource().getBytes(StandardCharsets.UTF_8));
46-
Files.write(sourceDir.resolve("java/lang/Throwable.java"), CleanTargetIntegrationTest.javaLangThrowableSource().getBytes(StandardCharsets.UTF_8));
47-
Files.write(sourceDir.resolve("java/lang/Exception.java"), CleanTargetIntegrationTest.javaLangExceptionSource().getBytes(StandardCharsets.UTF_8));
48-
Files.write(sourceDir.resolve("java/lang/RuntimeException.java"), CleanTargetIntegrationTest.javaLangRuntimeExceptionSource().getBytes(StandardCharsets.UTF_8));
49-
Files.write(sourceDir.resolve("java/lang/NullPointerException.java"), CleanTargetIntegrationTest.javaLangNullPointerExceptionSource().getBytes(StandardCharsets.UTF_8));
43+
writeJavaLangSources(sourceDir);
5044

5145
Path nativeReport = sourceDir.resolve("native_report.c");
5246
Files.write(nativeReport, nativeReportSource().getBytes(StandardCharsets.UTF_8));
5347

5448
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
5549
assertNotNull(compiler, "A JDK is required to compile test sources");
5650

51+
List<String> compileArgs = new ArrayList<>(Arrays.asList(
52+
"-d", classesDir.toString(),
53+
sourceDir.resolve("BytecodeInstructionApp.java").toString()
54+
));
55+
compileArgs.addAll(javaLangStubPaths(sourceDir));
5756
int compileResult = compiler.run(
5857
null,
5958
null,
6059
null,
61-
"-d", classesDir.toString(),
62-
sourceDir.resolve("BytecodeInstructionApp.java").toString(),
63-
sourceDir.resolve("java/lang/Object.java").toString(),
64-
sourceDir.resolve("java/lang/String.java").toString(),
65-
sourceDir.resolve("java/lang/Class.java").toString(),
66-
sourceDir.resolve("java/lang/Throwable.java").toString(),
67-
sourceDir.resolve("java/lang/Exception.java").toString(),
68-
sourceDir.resolve("java/lang/RuntimeException.java").toString(),
69-
sourceDir.resolve("java/lang/NullPointerException.java").toString()
60+
compileArgs.toArray(new String[0])
7061
);
7162
assertEquals(0, compileResult, "BytecodeInstructionApp should compile");
7263

@@ -111,6 +102,136 @@ void translatesOptimizedBytecodeToLLVMExecutable() throws Exception {
111102
assertTrue(output.contains("RESULT=293"), "Compiled program should print the expected arithmetic result");
112103
}
113104

105+
private String invokeLdcLocalVarsAppSource() {
106+
return "public class InvokeLdcLocalVarsApp {\n" +
107+
" private static native void report(int value);\n" +
108+
" private interface Multiplier { int multiply(int a, int b); }\n" +
109+
" private static class MultiplierImpl implements Multiplier {\n" +
110+
" public int multiply(int a, int b) { return a * b; }\n" +
111+
" }\n" +
112+
" private final int seed;\n" +
113+
" public InvokeLdcLocalVarsApp(int seed) {\n" +
114+
" this.seed = seed;\n" +
115+
" }\n" +
116+
" private int constantsAndLocals(int extra) {\n" +
117+
" int intVal = 21;\n" +
118+
" long min = Long.MIN_VALUE;\n" +
119+
" float nan = Float.NaN;\n" +
120+
" double posInf = Double.POSITIVE_INFINITY;\n" +
121+
" String label = \"TranslatorLdc\";\n" +
122+
" Class objectType = java.util.ArrayList.class;\n" +
123+
" Class arrayType = String[][].class;\n" +
124+
" int[] counts = new int[] { seed, extra, 12 };\n" +
125+
" int labelBonus = label != null ? 4 : 0;\n" +
126+
" int classTally = (objectType != null ? 9 : 0) + (arrayType != null ? 20 : 0);\n" +
127+
" byte small = 2;\n" +
128+
" short shorty = 12;\n" +
129+
" boolean flag = Double.isInfinite(posInf);\n" +
130+
" char initial = 'Z';\n" +
131+
" float nanFallback = nan;\n" +
132+
" double sum = posInf;\n" +
133+
" if (nan != nanFallback) {\n" +
134+
" return -1;\n" +
135+
" }\n" +
136+
" return intVal + counts.length + labelBonus + classTally + (flag ? 5 : 0) + small + shorty + initial + (min == Long.MIN_VALUE ? 7 : 0) + extra + seed + (sum > 0 ? 3 : 0);\n" +
137+
" }\n" +
138+
" private static int staticInvoke(int value) {\n" +
139+
" return value * 3;\n" +
140+
" }\n" +
141+
" private int instanceInvoke(int value) {\n" +
142+
" return value + seed;\n" +
143+
" }\n" +
144+
" private static int interfaceInvoke(Multiplier multiplier, int a, int b) {\n" +
145+
" return multiplier.multiply(a, b);\n" +
146+
" }\n" +
147+
" public static void main(String[] args) {\n" +
148+
" InvokeLdcLocalVarsApp app = new InvokeLdcLocalVarsApp(4);\n" +
149+
" Multiplier multiplier = new MultiplierImpl();\n" +
150+
" int combined = staticInvoke(5)\n" +
151+
" + app.instanceInvoke(6)\n" +
152+
" + interfaceInvoke(multiplier, 2, 7)\n" +
153+
" + app.constantsAndLocals(3);\n" +
154+
" report(combined);\n" +
155+
" }\n" +
156+
"}\n";
157+
}
158+
159+
@Test
160+
void translatesInvokeAndLdcBytecodeToLLVMExecutable() throws Exception {
161+
Parser.cleanup();
162+
163+
Path sourceDir = Files.createTempDirectory("invoke-ldc-sources");
164+
Path classesDir = Files.createTempDirectory("invoke-ldc-classes");
165+
166+
Files.createDirectories(sourceDir.resolve("java/lang"));
167+
Files.write(sourceDir.resolve("InvokeLdcLocalVarsApp.java"), invokeLdcLocalVarsAppSource().getBytes(StandardCharsets.UTF_8));
168+
writeJavaLangSources(sourceDir);
169+
170+
Path nativeReport = sourceDir.resolve("native_report.c");
171+
Files.write(nativeReport, nativeReportSource().getBytes(StandardCharsets.UTF_8));
172+
173+
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
174+
assertNotNull(compiler, "A JDK is required to compile test sources");
175+
176+
List<String> compileArgs = new ArrayList<>(Arrays.asList(
177+
"-d", classesDir.toString(),
178+
sourceDir.resolve("InvokeLdcLocalVarsApp.java").toString()
179+
));
180+
compileArgs.addAll(javaLangStubPaths(sourceDir));
181+
int compileResult = compiler.run(
182+
null,
183+
null,
184+
null,
185+
compileArgs.toArray(new String[0])
186+
);
187+
assertEquals(0, compileResult, "InvokeLdcLocalVarsApp should compile");
188+
189+
Files.copy(nativeReport, classesDir.resolve("native_report.c"));
190+
191+
Path outputDir = Files.createTempDirectory("invoke-ldc-output");
192+
CleanTargetIntegrationTest.runTranslator(classesDir, outputDir, "InvokeLdcLocalVars");
193+
194+
Path distDir = outputDir.resolve("dist");
195+
Path cmakeLists = distDir.resolve("CMakeLists.txt");
196+
assertTrue(Files.exists(cmakeLists), "Translator should emit a CMake project for Invoke/Ldc sample");
197+
198+
Path srcRoot = distDir.resolve("InvokeLdcLocalVars-src");
199+
CleanTargetIntegrationTest.patchCn1Globals(srcRoot);
200+
CleanTargetIntegrationTest.writeRuntimeStubs(srcRoot);
201+
writeInvokeLdcRuntimeStubs(srcRoot);
202+
203+
Path generatedSource = findGeneratedSource(srcRoot, "InvokeLdcLocalVarsApp");
204+
String generatedCode = new String(Files.readAllBytes(generatedSource), StandardCharsets.UTF_8);
205+
assertTrue(generatedCode.contains("0.0/0.0"), "NaN constants should translate through Ldc");
206+
assertTrue(generatedCode.contains("1.0 / 0.0"), "Infinite double constants should translate through Ldc");
207+
assertTrue(generatedCode.contains("LLONG_MIN"), "Long minimum value should pass through Ldc handling");
208+
assertTrue(generatedCode.contains("class_array2__java_lang_String"), "Array class literals should be emitted via Ldc");
209+
assertTrue(generatedCode.contains("class__java_util_ArrayList"), "Object class literals should be emitted via Ldc");
210+
assertTrue(generatedCode.contains("llocals_3_"), "Local variable generation should declare long locals");
211+
assertTrue(generatedCode.contains("locals[9].data.o"), "Local variable generation should declare object locals");
212+
assertTrue(generatedCode.contains("InvokeLdcLocalVarsApp_instanceInvoke"), "Virtual invokes should be routed through Invoke helper");
213+
assertTrue(generatedCode.contains("InvokeLdcLocalVarsApp_interfaceInvoke"), "Static invokes should include helper method routing");
214+
215+
CleanTargetIntegrationTest.replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString());
216+
217+
Path buildDir = distDir.resolve("build");
218+
Files.createDirectories(buildDir);
219+
220+
CleanTargetIntegrationTest.runCommand(Arrays.asList(
221+
"cmake",
222+
"-S", distDir.toString(),
223+
"-B", buildDir.toString(),
224+
"-DCMAKE_C_COMPILER=clang",
225+
"-DCMAKE_OBJC_COMPILER=clang"
226+
), distDir);
227+
228+
CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir);
229+
230+
Path executable = buildDir.resolve("InvokeLdcLocalVars");
231+
String output = CleanTargetIntegrationTest.runCommand(Arrays.asList(executable.toString()), buildDir);
232+
assertTrue(output.contains("RESULT="), "Compiled program should print the expected Invoke/Ldc result");
233+
}
234+
114235
private Set<String> snapshotArrayTypes() throws Exception {
115236
Field arrayTypesField = ByteCodeClass.class.getDeclaredField("arrayTypes");
116237
arrayTypesField.setAccessible(true);
@@ -302,14 +423,61 @@ void arrayLoadExpressionReducesAndAssigns() {
302423
}
303424

304425
private Path findGeneratedSource(Path srcRoot) throws Exception {
426+
return findGeneratedSource(srcRoot, "BytecodeInstructionApp");
427+
}
428+
429+
private Path findGeneratedSource(Path srcRoot, String classPrefix) throws Exception {
305430
try (Stream<Path> paths = Files.walk(srcRoot)) {
306431
return paths
307-
.filter(p -> p.getFileName().toString().startsWith("BytecodeInstructionApp") && p.getFileName().toString().endsWith(".c"))
432+
.filter(p -> p.getFileName().toString().startsWith(classPrefix) && p.getFileName().toString().endsWith(".c"))
433+
.sorted((a, b) -> Integer.compare(a.getFileName().toString().length(), b.getFileName().toString().length()))
308434
.findFirst()
309435
.orElseThrow(() -> new AssertionError("Translated source for BytecodeInstructionApp not found"));
310436
}
311437
}
312438

439+
private List<String> javaLangStubPaths(Path sourceDir) {
440+
return Arrays.asList(
441+
sourceDir.resolve("java/lang/Object.java").toString(),
442+
sourceDir.resolve("java/lang/String.java").toString(),
443+
sourceDir.resolve("java/lang/Class.java").toString(),
444+
sourceDir.resolve("java/lang/Throwable.java").toString(),
445+
sourceDir.resolve("java/lang/Exception.java").toString(),
446+
sourceDir.resolve("java/lang/RuntimeException.java").toString(),
447+
sourceDir.resolve("java/lang/NullPointerException.java").toString()
448+
);
449+
}
450+
451+
private void writeJavaLangSources(Path sourceDir) throws Exception {
452+
Files.write(sourceDir.resolve("java/lang/Object.java"), CleanTargetIntegrationTest.javaLangObjectSource().getBytes(StandardCharsets.UTF_8));
453+
Files.write(sourceDir.resolve("java/lang/String.java"), CleanTargetIntegrationTest.javaLangStringSource().getBytes(StandardCharsets.UTF_8));
454+
Files.write(sourceDir.resolve("java/lang/Class.java"), CleanTargetIntegrationTest.javaLangClassSource().getBytes(StandardCharsets.UTF_8));
455+
Files.write(sourceDir.resolve("java/lang/Throwable.java"), CleanTargetIntegrationTest.javaLangThrowableSource().getBytes(StandardCharsets.UTF_8));
456+
Files.write(sourceDir.resolve("java/lang/Exception.java"), CleanTargetIntegrationTest.javaLangExceptionSource().getBytes(StandardCharsets.UTF_8));
457+
Files.write(sourceDir.resolve("java/lang/RuntimeException.java"), CleanTargetIntegrationTest.javaLangRuntimeExceptionSource().getBytes(StandardCharsets.UTF_8));
458+
Files.write(sourceDir.resolve("java/lang/NullPointerException.java"), CleanTargetIntegrationTest.javaLangNullPointerExceptionSource().getBytes(StandardCharsets.UTF_8));
459+
}
460+
461+
private void writeInvokeLdcRuntimeStubs(Path srcRoot) throws Exception {
462+
Path doubleHeader = srcRoot.resolve("java_lang_Double.h");
463+
Path doubleSource = srcRoot.resolve("java_lang_Double.c");
464+
if (!Files.exists(doubleHeader)) {
465+
Files.write(doubleHeader, javaLangDoubleHeader().getBytes(StandardCharsets.UTF_8));
466+
}
467+
if (!Files.exists(doubleSource)) {
468+
Files.write(doubleSource, javaLangDoubleSource().getBytes(StandardCharsets.UTF_8));
469+
}
470+
471+
Path arrayListHeader = srcRoot.resolve("java_util_ArrayList.h");
472+
Path arrayListSource = srcRoot.resolve("java_util_ArrayList.c");
473+
if (!Files.exists(arrayListHeader)) {
474+
Files.write(arrayListHeader, javaUtilArrayListHeader().getBytes(StandardCharsets.UTF_8));
475+
}
476+
if (!Files.exists(arrayListSource)) {
477+
Files.write(arrayListSource, javaUtilArrayListSource().getBytes(StandardCharsets.UTF_8));
478+
}
479+
}
480+
313481
private String appSource() {
314482
return "public class BytecodeInstructionApp {\n" +
315483
" private static final int STATIC_INCREMENT = 3;\n" +
@@ -422,11 +590,38 @@ private String appSource() {
422590
"}\n";
423591
}
424592

593+
private String javaLangDoubleHeader() {
594+
return "#include \"cn1_globals.h\"\n" +
595+
"#include <limits.h>\n" +
596+
"JAVA_BOOLEAN java_lang_Double_isInfinite___double_R_boolean(CODENAME_ONE_THREAD_STATE, JAVA_DOUBLE value);\n";
597+
}
598+
599+
private String javaLangDoubleSource() {
600+
return "#include \"java_lang_Double.h\"\n" +
601+
"JAVA_BOOLEAN java_lang_Double_isInfinite___double_R_boolean(CODENAME_ONE_THREAD_STATE, JAVA_DOUBLE value) {\n" +
602+
" (void)threadStateData;\n" +
603+
" return value == (JAVA_DOUBLE)(1.0 / 0.0) || value == (JAVA_DOUBLE)(-1.0 / 0.0);\n" +
604+
"}\n";
605+
}
606+
607+
private String javaUtilArrayListHeader() {
608+
return "#include \"cn1_globals.h\"\n" +
609+
"extern struct clazz class__java_util_ArrayList;\n";
610+
}
611+
612+
private String javaUtilArrayListSource() {
613+
return "#include \"java_util_ArrayList.h\"\n" +
614+
"struct clazz class__java_util_ArrayList = {0};\n";
615+
}
616+
425617
private String nativeReportSource() {
426618
return "#include \"cn1_globals.h\"\n" +
427619
"#include <stdio.h>\n" +
428620
"void BytecodeInstructionApp_report___int(CODENAME_ONE_THREAD_STATE, JAVA_INT value) {\n" +
429621
" printf(\"RESULT=%d\\n\", value);\n" +
622+
"}\n" +
623+
"void InvokeLdcLocalVarsApp_report___int(CODENAME_ONE_THREAD_STATE, JAVA_INT value) {\n" +
624+
" printf(\"RESULT=%d\\n\", value);\n" +
430625
"}\n";
431626
}
432627
}

0 commit comments

Comments
 (0)