Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.codename1.tools.translator;
import org.junit.jupiter.api.Test;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;

class BytecodeInstructionIntegrationTest {

@Test
void translatesOptimizedBytecodeToLLVMExecutable() throws Exception {
Parser.cleanup();

Path sourceDir = Files.createTempDirectory("bytecode-integration-sources");
Path classesDir = Files.createTempDirectory("bytecode-integration-classes");

Files.createDirectories(sourceDir.resolve("java/lang"));
Files.write(sourceDir.resolve("BytecodeInstructionApp.java"), appSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("java/lang/Object.java"), CleanTargetIntegrationTest.javaLangObjectSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("java/lang/String.java"), CleanTargetIntegrationTest.javaLangStringSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("java/lang/Class.java"), CleanTargetIntegrationTest.javaLangClassSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("java/lang/Throwable.java"), CleanTargetIntegrationTest.javaLangThrowableSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("java/lang/Exception.java"), CleanTargetIntegrationTest.javaLangExceptionSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("java/lang/RuntimeException.java"), CleanTargetIntegrationTest.javaLangRuntimeExceptionSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("java/lang/NullPointerException.java"), CleanTargetIntegrationTest.javaLangNullPointerExceptionSource().getBytes(StandardCharsets.UTF_8));

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

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

int compileResult = compiler.run(
null,
null,
null,
"-d", classesDir.toString(),
sourceDir.resolve("BytecodeInstructionApp.java").toString(),
sourceDir.resolve("java/lang/Object.java").toString(),
sourceDir.resolve("java/lang/String.java").toString(),
sourceDir.resolve("java/lang/Class.java").toString(),
sourceDir.resolve("java/lang/Throwable.java").toString(),
sourceDir.resolve("java/lang/Exception.java").toString(),
sourceDir.resolve("java/lang/RuntimeException.java").toString(),
sourceDir.resolve("java/lang/NullPointerException.java").toString()
);
assertEquals(0, compileResult, "BytecodeInstructionApp should compile");

Files.copy(nativeReport, classesDir.resolve("native_report.c"));

Path outputDir = Files.createTempDirectory("bytecode-integration-output");
CleanTargetIntegrationTest.runTranslator(classesDir, outputDir, "CustomBytecodeApp");

Path distDir = outputDir.resolve("dist");
Path cmakeLists = distDir.resolve("CMakeLists.txt");
assertTrue(Files.exists(cmakeLists), "Translator should emit a CMake project for the optimized sample");

Path srcRoot = distDir.resolve("CustomBytecodeApp-src");
CleanTargetIntegrationTest.patchCn1Globals(srcRoot);
CleanTargetIntegrationTest.writeRuntimeStubs(srcRoot);

Path generatedSource = findGeneratedSource(srcRoot);
String generatedCode = new String(Files.readAllBytes(generatedSource), StandardCharsets.UTF_8);
assertTrue(generatedCode.contains("CustomJump */"), "Optimized comparisons should emit CustomJump code");
assertTrue(generatedCode.contains("BC_IINC"), "Increment operations should translate to BC_IINC macro");
assertTrue(generatedCode.contains("VarOp.assignFrom"), "Optimized stores should rely on CustomIntruction output");

CleanTargetIntegrationTest.replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString());

Path buildDir = distDir.resolve("build");
Files.createDirectories(buildDir);

CleanTargetIntegrationTest.runCommand(Arrays.asList(
"cmake",
"-S", distDir.toString(),
"-B", buildDir.toString(),
"-DCMAKE_C_COMPILER=clang",
"-DCMAKE_OBJC_COMPILER=clang"
), distDir);

CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir);

Path executable = buildDir.resolve("CustomBytecodeApp");
String output = CleanTargetIntegrationTest.runCommand(Arrays.asList(executable.toString()), buildDir);
assertTrue(output.contains("RESULT=14"), "Compiled program should print the expected arithmetic result");
}

private Path findGeneratedSource(Path srcRoot) throws Exception {
try (Stream<Path> paths = Files.walk(srcRoot)) {
return paths
.filter(p -> p.getFileName().toString().startsWith("BytecodeInstructionApp") && p.getFileName().toString().endsWith(".c"))
.findFirst()
.orElseThrow(() -> new AssertionError("Translated source for BytecodeInstructionApp not found"));
}
}

private String appSource() {
return "public class BytecodeInstructionApp {\n" +
" private static native void report(int value);\n" +
" private static int optimizedComputation(int a, int b) {\n" +
" int counter = a;\n" +
" counter++;\n" +
" int branchBase = counter;\n" +
" int min;\n" +
" if (branchBase < b) {\n" +
" min = branchBase;\n" +
" } else {\n" +
" min = b + 2;\n" +
" }\n" +
" int result = min + counter;\n" +
" return result;\n" +
" }\n" +
" public static void main(String[] args) {\n" +
" int first = optimizedComputation(1, 3);\n" +
" int second = optimizedComputation(5, 2);\n" +
" report(first + second);\n" +
" }\n" +
"}\n";
}

private String nativeReportSource() {
return "#include \"cn1_globals.h\"\n" +
"#include <stdio.h>\n" +
"void BytecodeInstructionApp_report___int(CODENAME_ONE_THREAD_STATE, JAVA_INT value) {\n" +
" printf(\"RESULT=%d\\n\", value);\n" +
"}\n";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ void generatesRunnableHelloWorldUsingCleanTarget() throws Exception {
"Compiled program should print hello message, actual output was:\n" + output);
}

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

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

private String runCommand(List<String> command, Path workingDir) throws Exception {
static String runCommand(List<String> command, Path workingDir) throws Exception {
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(workingDir.toFile());
builder.redirectErrorStream(true);
Expand All @@ -164,7 +164,7 @@ private String runCommand(List<String> command, Path workingDir) throws Exceptio
return output;
}

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

private void writeRuntimeStubs(Path srcRoot) throws IOException {
static void writeRuntimeStubs(Path srcRoot) throws IOException {
Path stubs = srcRoot.resolve("runtime_stubs.c");
if (Files.exists(stubs)) {
return;
Expand Down Expand Up @@ -243,7 +243,7 @@ private void writeRuntimeStubs(Path srcRoot) throws IOException {
Files.write(stubs, content.getBytes(StandardCharsets.UTF_8));
}

private String helloWorldSource() {
static String helloWorldSource() {
return "public class HelloWorld {\n" +
" private static native void nativeHello();\n" +
" public static void main(String[] args) {\n" +
Expand All @@ -252,49 +252,49 @@ private String helloWorldSource() {
"}\n";
}

private String javaLangObjectSource() {
static String javaLangObjectSource() {
return "package java.lang;\n" +
"public class Object {\n" +
"}\n";
}

private String javaLangStringSource() {
static String javaLangStringSource() {
return "package java.lang;\n" +
"public class String extends Object {\n" +
"}\n";
}

private String javaLangClassSource() {
static String javaLangClassSource() {
return "package java.lang;\n" +
"public final class Class extends Object {\n" +
"}\n";
}

private String javaLangThrowableSource() {
static String javaLangThrowableSource() {
return "package java.lang;\n" +
"public class Throwable extends Object {\n" +
"}\n";
}

private String javaLangExceptionSource() {
static String javaLangExceptionSource() {
return "package java.lang;\n" +
"public class Exception extends Throwable {\n" +
"}\n";
}

private String javaLangRuntimeExceptionSource() {
static String javaLangRuntimeExceptionSource() {
return "package java.lang;\n" +
"public class RuntimeException extends Exception {\n" +
"}\n";
}

private String javaLangNullPointerExceptionSource() {
static String javaLangNullPointerExceptionSource() {
return "package java.lang;\n" +
"public class NullPointerException extends RuntimeException {\n" +
"}\n";
}

private String nativeHelloSource() {
static String nativeHelloSource() {
return "#include \"cn1_globals.h\"\n" +
"#include <stdio.h>\n" +
"void HelloWorld_nativeHello__(CODENAME_ONE_THREAD_STATE) {\n" +
Expand Down
Loading