@@ -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