Introduction to Sulong
Sulong is a high-performance LLVM bitcode interpreter built on the GraalVM. It allows execution of C/C++ code directly on the Java Virtual Machine (JVM), enabling seamless interoperability between Java and native code.
Table of Contents
- Sulong Architecture & Setup
- Basic C Code Execution
- C++ Code Execution
- Java-Native Interoperability
- Memory Management
- Performance Optimization
- Advanced Use Cases
- Troubleshooting & Best Practices
Sulong Architecture & Setup
GraalVM Setup with Sulong
// Sulong requires GraalVM with LLVM toolchain support
public class SulongSetup {
public static void checkSulongAvailability() {
try {
// Check if we're running on GraalVM
String vmName = System.getProperty("java.vm.name");
System.out.println("VM Name: " + vmName);
// Check Sulong availability
Class<?> sulongClass = Class.forName("com.oracle.truffle.llvm.runtime.LLVMLanguage");
System.out.println("Sulong is available: " + (sulongClass != null));
// Check GraalVM version
String graalVersion = System.getProperty("graalvm.version");
System.out.println("GraalVM Version: " + graalVersion);
} catch (ClassNotFoundException e) {
System.err.println("Sulong is not available. Please use GraalVM with LLVM support.");
}
}
public static void main(String[] args) {
checkSulongAvailability();
}
}
Maven Dependencies
<dependencies> <!-- GraalVM SDK --> <dependency> <groupId>org.graalvm.sdk</groupId> <artifactId>graal-sdk</artifactId> <version>22.3.0</version> </dependency> <!-- Truffle API --> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-api</artifactId> <version>22.3.0</version> </dependency> <!-- Sulong --> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-llvm</artifactId> <version>22.3.0</version> </dependency> <!-- For C/C++ compilation --> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-llvm-toolchain</artifactId> <version>22.3.0</version> </dependency> </dependencies>
Basic C Code Execution
Simple C Function Execution
math_operations.c
#include <stdio.h>
// Simple arithmetic operations
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
double divide(int a, int b) {
if (b != 0) {
return (double)a / b;
}
return 0.0;
}
// String operations
void greet(char* name) {
printf("Hello, %s!\n", name);
}
int string_length(char* str) {
int length = 0;
while (str[length] != '\0') {
length++;
}
return length;
}
MathOperations.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
public class MathOperations {
private final Context context;
private final Value cLibrary;
public MathOperations() {
// Create GraalVM context with LLVM support
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
// Compile and load C code
Path cSource = Paths.get("math_operations.c");
Path bitcodeFile = compileCToBitcode(cSource);
this.cLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCToBitcode(Path cSource) {
try {
Path bitcodeFile = Files.createTempFile("math_operations", ".bc");
// Use clang to compile C to LLVM bitcode
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("C compilation failed with exit code: " + exitCode);
}
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Failed to compile C code", e);
}
}
public int add(int a, int b) {
Value addFunction = cLibrary.getMember("add");
return addFunction.execute(a, b).asInt();
}
public int subtract(int a, int b) {
Value subtractFunction = cLibrary.getMember("subtract");
return subtractFunction.execute(a, b).asInt();
}
public int multiply(int a, int b) {
Value multiplyFunction = cLibrary.getMember("multiply");
return multiplyFunction.execute(a, b).asInt();
}
public double divide(int a, int b) {
Value divideFunction = cLibrary.getMember("divide");
return divideFunction.execute(a, b).asDouble();
}
public void greet(String name) {
Value greetFunction = cLibrary.getMember("greet");
greetFunction.execute(name);
}
public int stringLength(String str) {
Value lengthFunction = cLibrary.getMember("string_length");
return lengthFunction.execute(str).asInt();
}
public void close() {
context.close();
}
public static void main(String[] args) {
MathOperations math = new MathOperations();
try {
// Test arithmetic operations
System.out.println("10 + 5 = " + math.add(10, 5));
System.out.println("10 - 5 = " + math.subtract(10, 5));
System.out.println("10 * 5 = " + math.multiply(10, 5));
System.out.println("10 / 3 = " + math.divide(10, 3));
// Test string operations
math.greet("Java Developer");
System.out.println("Length of 'Hello': " + math.stringLength("Hello"));
} finally {
math.close();
}
}
}
Array Operations in C
array_operations.c
#include <stdio.h>
// Array sum
int array_sum(int* arr, int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return sum;
}
// Array maximum
int array_max(int* arr, int size) {
int max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// Array reverse
void reverse_array(int* arr, int size) {
int start = 0;
int end = size - 1;
while (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end--;
}
}
// Matrix multiplication
void matrix_multiply(int* a, int* b, int* result, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[i * cols + j] = 0;
for (int k = 0; k < cols; k++) {
result[i * cols + j] += a[i * cols + k] * b[k * cols + j];
}
}
}
}
ArrayOperations.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
import java.util.Arrays;
public class ArrayOperations {
private final Context context;
private final Value cLibrary;
public ArrayOperations() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
Path bitcodeFile = compileCToBitcode(Paths.get("array_operations.c"));
this.cLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCToBitcode(Path cSource) {
try {
Path bitcodeFile = Files.createTempFile("array_operations", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm", "-O2",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
process.waitFor();
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Compilation failed", e);
}
}
public int arraySum(int[] array) {
// Create a polyglot array from Java array
Value polyglotArray = context.asValue(array);
Value sumFunction = cLibrary.getMember("array_sum");
return sumFunction.execute(polyglotArray, array.length).asInt();
}
public int arrayMax(int[] array) {
Value polyglotArray = context.asValue(array);
Value maxFunction = cLibrary.getMember("array_max");
return maxFunction.execute(polyglotArray, array.length).asInt();
}
public int[] reverseArray(int[] array) {
int[] copy = Arrays.copyOf(array, array.length);
Value polyglotArray = context.asValue(copy);
Value reverseFunction = cLibrary.getMember("reverse_array");
reverseFunction.execute(polyglotArray, copy.length);
return copy;
}
public int[] matrixMultiply(int[] a, int[] b, int size) {
int[] result = new int[size * size];
Value aArray = context.asValue(a);
Value bArray = context.asValue(b);
Value resultArray = context.asValue(result);
Value multiplyFunction = cLibrary.getMember("matrix_multiply");
multiplyFunction.execute(aArray, bArray, resultArray, size, size);
return result;
}
public void close() {
context.close();
}
public static void main(String[] args) {
ArrayOperations arrays = new ArrayOperations();
try {
// Test array operations
int[] testArray = {1, 2, 3, 4, 5};
System.out.println("Original array: " + Arrays.toString(testArray));
System.out.println("Sum: " + arrays.arraySum(testArray));
System.out.println("Max: " + arrays.arrayMax(testArray));
int[] reversed = arrays.reverseArray(testArray);
System.out.println("Reversed: " + Arrays.toString(reversed));
// Test matrix multiplication
int[] matrixA = {1, 2, 3, 4}; // 2x2 matrix
int[] matrixB = {2, 0, 1, 2}; // 2x2 matrix
int[] result = arrays.matrixMultiply(matrixA, matrixB, 2);
System.out.println("Matrix multiplication result: " + Arrays.toString(result));
} finally {
arrays.close();
}
}
}
C++ Code Execution
C++ Classes and Objects
cpp_operations.cpp
#include <iostream>
#include <vector>
#include <string>
// Simple C++ class
class Calculator {
private:
double memory;
public:
Calculator() : memory(0.0) {}
double add(double a, double b) {
memory = a + b;
return memory;
}
double subtract(double a, double b) {
memory = a - b;
return memory;
}
double multiply(double a, double b) {
memory = a * b;
return memory;
}
double divide(double a, double b) {
if (b != 0.0) {
memory = a / b;
return memory;
}
return 0.0;
}
double getMemory() {
return memory;
}
void clearMemory() {
memory = 0.0;
}
};
// C++ STL usage
extern "C" {
// Bridge functions for Java interop
Calculator* create_calculator() {
return new Calculator();
}
void delete_calculator(Calculator* calc) {
delete calc;
}
double calculator_add(Calculator* calc, double a, double b) {
return calc->add(a, b);
}
double calculator_multiply(Calculator* calc, double a, double b) {
return calc->multiply(a, b);
}
double calculator_get_memory(Calculator* calc) {
return calc->getMemory();
}
void calculator_clear_memory(Calculator* calc) {
calc->clearMemory();
}
}
// String operations with C++
extern "C" {
char* concatenate_strings(const char* str1, const char* str2) {
std::string result = std::string(str1) + std::string(str2);
char* c_str = new char[result.length() + 1];
std::strcpy(c_str, result.c_str());
return c_str;
}
void free_string(char* str) {
delete[] str;
}
}
CppOperations.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
public class CppOperations {
private final Context context;
private final Value cppLibrary;
public CppOperations() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
Path bitcodeFile = compileCppToBitcode(Paths.get("cpp_operations.cpp"));
this.cppLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCppToBitcode(Path cppSource) {
try {
Path bitcodeFile = Files.createTempFile("cpp_operations", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang++", "-c", "-emit-llvm", "-std=c++14", "-O2",
"-o", bitcodeFile.toString(),
cppSource.toString()
);
Process process = pb.start();
process.waitFor();
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("C++ compilation failed", e);
}
}
public Calculator createCalculator() {
return new Calculator();
}
public String concatenateStrings(String str1, String str2) {
Value concatFunction = cppLibrary.getMember("concatenate_strings");
Value result = concatFunction.execute(str1, str2);
String concatenated = result.asString();
// Free the allocated C++ memory
Value freeFunction = cppLibrary.getMember("free_string");
freeFunction.execute(result);
return concatenated;
}
public class Calculator {
private final Value calculatorPtr;
private Calculator() {
Value createFunction = cppLibrary.getMember("create_calculator");
this.calculatorPtr = createFunction.execute();
}
public double add(double a, double b) {
Value addFunction = cppLibrary.getMember("calculator_add");
return addFunction.execute(calculatorPtr, a, b).asDouble();
}
public double multiply(double a, double b) {
Value multiplyFunction = cppLibrary.getMember("calculator_multiply");
return multiplyFunction.execute(calculatorPtr, a, b).asDouble();
}
public double getMemory() {
Value getMemoryFunction = cppLibrary.getMember("calculator_get_memory");
return getMemoryFunction.execute(calculatorPtr).asDouble();
}
public void clearMemory() {
Value clearFunction = cppLibrary.getMember("calculator_clear_memory");
clearFunction.execute(calculatorPtr);
}
public void close() {
Value deleteFunction = cppLibrary.getMember("delete_calculator");
deleteFunction.execute(calculatorPtr);
}
}
public void close() {
context.close();
}
public static void main(String[] args) {
CppOperations cppOps = new CppOperations();
try {
// Test C++ class
Calculator calc = cppOps.createCalculator();
System.out.println("5.5 + 3.2 = " + calc.add(5.5, 3.2));
System.out.println("Memory: " + calc.getMemory());
System.out.println("4.0 * 2.5 = " + calc.multiply(4.0, 2.5));
System.out.println("Memory: " + calc.getMemory());
calc.clearMemory();
System.out.println("After clear - Memory: " + calc.getMemory());
calc.close();
// Test string operations
String result = cppOps.concatenateStrings("Hello, ", "C++ from Java!");
System.out.println("Concatenated: " + result);
} finally {
cppOps.close();
}
}
}
Java-Native Interoperability
Callback Mechanisms
callbacks.c
#include <stdio.h>
// Function pointer types
typedef int (*BinaryOperation)(int, int);
typedef void (*Callback)(const char*);
// Function that accepts a callback
void process_with_callback(const char* message, Callback callback) {
printf("Processing: %s\n", message);
if (callback != NULL) {
callback("Callback executed from C");
}
}
// Function that uses function pointers
int execute_operation(int a, int b, BinaryOperation operation) {
if (operation != NULL) {
return operation(a, b);
}
return 0;
}
// Array processing with callback
void process_array(int* array, int size, BinaryOperation operation) {
for (int i = 0; i < size; i++) {
if (operation != NULL) {
array[i] = operation(array[i], i);
}
}
}
CallbackInterop.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Consumer;
public class CallbackInterop {
private final Context context;
private final Value cLibrary;
public CallbackInterop() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
Path bitcodeFile = compileCToBitcode(Paths.get("callbacks.c"));
this.cLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCToBitcode(Path cSource) {
try {
Path bitcodeFile = Files.createTempFile("callbacks", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm", "-O2",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
process.waitFor();
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Compilation failed", e);
}
}
public void processWithCallback(String message, Consumer<String> javaCallback) {
Value callbackFunction = cLibrary.getMember("process_with_callback");
// Create a C callback that calls the Java consumer
Value cCallback = Value.asValue(new Callback() {
@Override
public void execute(Value... args) {
String callbackMessage = args[0].asString();
javaCallback.accept(callbackMessage);
}
});
callbackFunction.execute(message, cCallback);
}
public int executeOperation(int a, int b, BiFunction<Integer, Integer, Integer> javaOperation) {
Value operationFunction = cLibrary.getMember("execute_operation");
// Create a C function pointer that calls the Java BiFunction
Value cOperation = Value.asValue(new BinaryOperation() {
@Override
public int execute(Value... args) {
int x = args[0].asInt();
int y = args[1].asInt();
return javaOperation.apply(x, y);
}
});
return operationFunction.execute(a, b, cOperation).asInt();
}
public int[] processArray(int[] array, BiFunction<Integer, Integer, Integer> operation) {
int[] copy = Arrays.copyOf(array, array.length);
Value arrayValue = context.asValue(copy);
Value processArrayFunction = cLibrary.getMember("process_array");
Value cOperation = Value.asValue(new BinaryOperation() {
@Override
public int execute(Value... args) {
int value = args[0].asInt();
int index = args[1].asInt();
return operation.apply(value, index);
}
});
processArrayFunction.execute(arrayValue, copy.length, cOperation);
return copy;
}
// Functional interfaces for callbacks
@FunctionalInterface
public interface Callback {
void execute(Value... args);
}
@FunctionalInterface
public interface BinaryOperation {
int execute(Value... args);
}
public void close() {
context.close();
}
public static void main(String[] args) {
CallbackInterop interop = new CallbackInterop();
try {
// Test callback from C to Java
System.out.println("=== Callback Test ===");
interop.processWithCallback("Hello from Java", message -> {
System.out.println("Java received: " + message);
});
// Test function pointer operations
System.out.println("\n=== Function Pointer Test ===");
BiFunction<Integer, Integer, Integer> powerOp = (a, b) -> (int) Math.pow(a, b);
int result = interop.executeOperation(2, 3, powerOp);
System.out.println("2^3 = " + result);
// Test array processing with callback
System.out.println("\n=== Array Processing Test ===");
int[] array = {1, 2, 3, 4, 5};
System.out.println("Original: " + Arrays.toString(array));
int[] processed = interop.processArray(array, (value, index) -> value * index);
System.out.println("Processed: " + Arrays.toString(processed));
} finally {
interop.close();
}
}
}
Complex Data Structure Interop
data_structures.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Complex data structure
typedef struct {
int id;
char name[50];
double salary;
int department_id;
} Employee;
typedef struct {
Employee* employees;
int count;
int capacity;
} EmployeeDatabase;
// Database operations
EmployeeDatabase* create_database(int initial_capacity) {
EmployeeDatabase* db = (EmployeeDatabase*)malloc(sizeof(EmployeeDatabase));
db->employees = (Employee*)malloc(initial_capacity * sizeof(Employee));
db->count = 0;
db->capacity = initial_capacity;
return db;
}
void free_database(EmployeeDatabase* db) {
if (db != NULL) {
free(db->employees);
free(db);
}
}
int add_employee(EmployeeDatabase* db, int id, const char* name, double salary, int dept_id) {
if (db->count >= db->capacity) {
// Resize array
int new_capacity = db->capacity * 2;
Employee* new_employees = (Employee*)realloc(db->employees, new_capacity * sizeof(Employee));
if (new_employees == NULL) {
return 0; // Failure
}
db->employees = new_employees;
db->capacity = new_capacity;
}
Employee* emp = &db->employees[db->count];
emp->id = id;
strncpy(emp->name, name, sizeof(emp->name) - 1);
emp->name[sizeof(emp->name) - 1] = '\0';
emp->salary = salary;
emp->department_id = dept_id;
db->count++;
return 1; // Success
}
double calculate_total_salary(EmployeeDatabase* db) {
double total = 0.0;
for (int i = 0; i < db->count; i++) {
total += db->employees[i].salary;
}
return total;
}
Employee* find_employee_by_id(EmployeeDatabase* db, int id) {
for (int i = 0; i < db->count; i++) {
if (db->employees[i].id == id) {
return &db->employees[i];
}
}
return NULL;
}
DataStructureInterop.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
import java.util.*;
public class DataStructureInterop {
private final Context context;
private final Value cLibrary;
public DataStructureInterop() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
Path bitcodeFile = compileCToBitcode(Paths.get("data_structures.c"));
this.cLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCToBitcode(Path cSource) {
try {
Path bitcodeFile = Files.createTempFile("data_structures", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm", "-O2",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
process.waitFor();
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Compilation failed", e);
}
}
public EmployeeDatabase createDatabase(int initialCapacity) {
return new EmployeeDatabase(initialCapacity);
}
public class EmployeeDatabase {
private final Value databasePtr;
private EmployeeDatabase(int initialCapacity) {
Value createFunction = cLibrary.getMember("create_database");
this.databasePtr = createFunction.execute(initialCapacity);
}
public boolean addEmployee(int id, String name, double salary, int departmentId) {
Value addFunction = cLibrary.getMember("add_employee");
Value result = addFunction.execute(databasePtr, id, name, salary, departmentId);
return result.asBoolean();
}
public double calculateTotalSalary() {
Value totalFunction = cLibrary.getMember("calculate_total_salary");
return totalFunction.execute(databasePtr).asDouble();
}
public Optional<Employee> findEmployeeById(int id) {
Value findFunction = cLibrary.getMember("find_employee_by_id");
Value employeePtr = findFunction.execute(databasePtr, id);
if (employeePtr.isNull()) {
return Optional.empty();
}
return Optional.of(new Employee(employeePtr));
}
public int getEmployeeCount() {
// Access struct field directly
return databasePtr.getMember("count").asInt();
}
public void close() {
Value freeFunction = cLibrary.getMember("free_database");
freeFunction.execute(databasePtr);
}
}
public class Employee {
private final Value employeePtr;
private Employee(Value employeePtr) {
this.employeePtr = employeePtr;
}
public int getId() {
return employeePtr.getMember("id").asInt();
}
public String getName() {
return employeePtr.getMember("name").asString();
}
public double getSalary() {
return employeePtr.getMember("salary").asDouble();
}
public int getDepartmentId() {
return employeePtr.getMember("department_id").asInt();
}
@Override
public String toString() {
return String.format("Employee{id=%d, name='%s', salary=%.2f, dept=%d}",
getId(), getName(), getSalary(), getDepartmentId());
}
}
public void close() {
context.close();
}
public static void main(String[] args) {
DataStructureInterop interop = new DataStructureInterop();
try {
// Create employee database
EmployeeDatabase db = interop.createDatabase(10);
// Add employees
db.addEmployee(1, "John Doe", 50000.0, 101);
db.addEmployee(2, "Jane Smith", 60000.0, 102);
db.addEmployee(3, "Bob Johnson", 55000.0, 101);
System.out.println("Employee count: " + db.getEmployeeCount());
System.out.println("Total salary: $" + db.calculateTotalSalary());
// Find employee
Optional<Employee> employee = db.findEmployeeById(2);
employee.ifPresent(emp -> {
System.out.println("Found employee: " + emp);
});
// Print all employees
for (int i = 1; i <= db.getEmployeeCount(); i++) {
db.findEmployeeById(i).ifPresent(System.out::println);
}
db.close();
} finally {
interop.close();
}
}
}
Memory Management
Manual Memory Management
memory_management.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Memory allocation tracking
typedef struct {
void** pointers;
int count;
int capacity;
} MemoryTracker;
MemoryTracker* create_memory_tracker() {
MemoryTracker* tracker = (MemoryTracker*)malloc(sizeof(MemoryTracker));
tracker->capacity = 10;
tracker->count = 0;
tracker->pointers = (void**)malloc(tracker->capacity * sizeof(void*));
return tracker;
}
void track_allocation(MemoryTracker* tracker, void* ptr) {
if (tracker->count >= tracker->capacity) {
tracker->capacity *= 2;
tracker->pointers = (void**)realloc(tracker->pointers, tracker->capacity * sizeof(void*));
}
tracker->pointers[tracker->count++] = ptr;
}
void* tracked_malloc(MemoryTracker* tracker, size_t size) {
void* ptr = malloc(size);
if (ptr != NULL) {
track_allocation(tracker, ptr);
}
return ptr;
}
void free_all_memory(MemoryTracker* tracker) {
for (int i = 0; i < tracker->count; i++) {
free(tracker->pointers[i]);
}
free(tracker->pointers);
free(tracker);
}
// String operations with manual memory management
char* create_greeting(const char* name) {
size_t length = strlen(name) + 8; // "Hello, " + name + "!"
char* greeting = (char*)malloc(length);
if (greeting != NULL) {
snprintf(greeting, length, "Hello, %s!", name);
}
return greeting;
}
// Array operations
int* create_int_array(int size) {
return (int*)malloc(size * sizeof(int));
}
void fill_int_array(int* array, int size, int value) {
for (int i = 0; i < size; i++) {
array[i] = value + i;
}
}
int sum_int_array(int* array, int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += array[i];
}
return sum;
}
MemoryManagement.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
import java.lang.ref.Cleaner;
public class MemoryManagement {
private final Context context;
private final Value cLibrary;
private final Cleaner cleaner;
public MemoryManagement() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
this.cleaner = Cleaner.create();
Path bitcodeFile = compileCToBitcode(Paths.get("memory_management.c"));
this.cLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCToBitcode(Path cSource) {
try {
Path bitcodeFile = Files.createTempFile("memory_management", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm", "-O2",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
process.waitFor();
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Compilation failed", e);
}
}
public MemoryTracker createMemoryTracker() {
return new MemoryTracker();
}
public class MemoryTracker implements AutoCloseable {
private final Value trackerPtr;
private final Cleaner.Cleanable cleanable;
private MemoryTracker() {
Value createFunction = cLibrary.getMember("create_memory_tracker");
this.trackerPtr = createFunction.execute();
// Register cleanup
this.cleanable = cleaner.register(this, new CleanupAction(trackerPtr));
}
public String createGreeting(String name) {
Value createGreetingFunction = cLibrary.getMember("create_greeting");
Value greetingPtr = createGreetingFunction.execute(name);
if (greetingPtr.isNull()) {
throw new RuntimeException("Failed to create greeting");
}
// Track this allocation
Value trackFunction = cLibrary.getMember("track_allocation");
trackFunction.execute(trackerPtr, greetingPtr);
String greeting = greetingPtr.asString();
return greeting;
}
public IntArray createIntArray(int size) {
Value createArrayFunction = cLibrary.getMember("create_int_array");
Value arrayPtr = createArrayFunction.execute(size);
if (arrayPtr.isNull()) {
throw new RuntimeException("Failed to create array");
}
// Track this allocation
Value trackFunction = cLibrary.getMember("track_allocation");
trackFunction.execute(trackerPtr, arrayPtr);
return new IntArray(arrayPtr, size);
}
@Override
public void close() {
if (cleanable != null) {
cleanable.clean();
}
}
private static class CleanupAction implements Runnable {
private final Value trackerPtr;
CleanupAction(Value trackerPtr) {
this.trackerPtr = trackerPtr;
}
@Override
public void run() {
Value freeFunction = trackerPtr.getContext().getBindings("llvm")
.getMember("free_all_memory");
freeFunction.execute(trackerPtr);
}
}
}
public class IntArray {
private final Value arrayPtr;
private final int size;
private IntArray(Value arrayPtr, int size) {
this.arrayPtr = arrayPtr;
this.size = size;
}
public void fill(int value) {
Value fillFunction = cLibrary.getMember("fill_int_array");
fillFunction.execute(arrayPtr, size, value);
}
public int sum() {
Value sumFunction = cLibrary.getMember("sum_int_array");
return sumFunction.execute(arrayPtr, size).asInt();
}
public int[] toJavaArray() {
int[] javaArray = new int[size];
for (int i = 0; i < size; i++) {
// Access array element through pointer arithmetic
Value element = arrayPtr.readArrayElement(i);
javaArray[i] = element.asInt();
}
return javaArray;
}
}
public void close() {
context.close();
}
public static void main(String[] args) {
MemoryManagement memoryMgmt = new MemoryManagement();
try (MemoryTracker tracker = memoryMgmt.createMemoryTracker()) {
// Test string allocation
String greeting = tracker.createGreeting("Memory Management");
System.out.println("Greeting: " + greeting);
// Test array allocation and operations
IntArray array = tracker.createIntArray(5);
array.fill(10);
System.out.println("Array sum: " + array.sum());
System.out.println("Java array: " + java.util.Arrays.toString(array.toJavaArray()));
} catch (Exception e) {
e.printStackTrace();
} finally {
memoryMgmt.close();
}
}
}
Performance Optimization
Performance Comparison
performance.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// Matrix operations for performance testing
void matrix_multiply(double* a, double* b, double* result, int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
result[i * n + j] = 0.0;
for (int k = 0; k < n; k++) {
result[i * n + j] += a[i * n + k] * b[k * n + j];
}
}
}
}
// Quick sort implementation
void quick_sort(int* arr, int low, int high) {
if (low < high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
int pi = i + 1;
quick_sort(arr, low, pi - 1);
quick_sort(arr, pi + 1, high);
}
}
// Fibonacci calculation
long long fibonacci(int n) {
if (n <= 1) return n;
long long a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}
PerformanceBenchmark.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class PerformanceBenchmark {
private final Context context;
private final Value cLibrary;
public PerformanceBenchmark() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
Path bitcodeFile = compileCToBitcode(Paths.get("performance.c"));
this.cLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCToBitcode(Path cSource) {
try {
Path bitcodeFile = Files.createTempFile("performance", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm", "-O3", "-march=native",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
process.waitFor();
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Compilation failed", e);
}
}
// C implementation
public double[] matrixMultiplyC(double[] a, double[] b, int n) {
double[] result = new double[n * n];
Value aArray = context.asValue(a);
Value bArray = context.asValue(b);
Value resultArray = context.asValue(result);
Value multiplyFunction = cLibrary.getMember("matrix_multiply");
multiplyFunction.execute(aArray, bArray, resultArray, n);
return result;
}
// Java implementation for comparison
public double[] matrixMultiplyJava(double[] a, double[] b, int n) {
double[] result = new double[n * n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
result[i * n + j] = 0.0;
for (int k = 0; k < n; k++) {
result[i * n + j] += a[i * n + k] * b[k * n + j];
}
}
}
return result;
}
// C quicksort
public int[] quickSortC(int[] array) {
int[] copy = Arrays.copyOf(array, array.length);
Value arrayValue = context.asValue(copy);
Value sortFunction = cLibrary.getMember("quick_sort");
sortFunction.execute(arrayValue, 0, copy.length - 1);
return copy;
}
// Java quicksort for comparison
public int[] quickSortJava(int[] array) {
int[] copy = Arrays.copyOf(array, array.length);
quickSortJava(copy, 0, copy.length - 1);
return copy;
}
private void quickSortJava(int[] arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSortJava(arr, low, pi - 1);
quickSortJava(arr, pi + 1, high);
}
}
private int partition(int[] arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] < pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
// Fibonacci C
public long fibonacciC(int n) {
Value fibFunction = cLibrary.getMember("fibonacci");
return fibFunction.execute(n).asLong();
}
// Fibonacci Java
public long fibonacciJava(int n) {
if (n <= 1) return n;
long a = 0, b = 1, c;
for (int i = 2; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return b;
}
public void benchmark(String name, Runnable task, int iterations) {
System.out.println("\n=== " + name + " ===");
// Warmup
for (int i = 0; i < iterations / 2; i++) {
task.run();
}
// Measurement
long startTime = System.nanoTime();
for (int i = 0; i < iterations; i++) {
task.run();
}
long endTime = System.nanoTime();
double averageTime = (endTime - startTime) / (double) iterations / 1_000_000.0;
System.out.printf("Average time: %.3f ms%n", averageTime);
}
public void close() {
context.close();
}
public static void main(String[] args) {
PerformanceBenchmark benchmark = new PerformanceBenchmark();
try {
// Matrix multiplication benchmark
int matrixSize = 100;
double[] matrixA = new double[matrixSize * matrixSize];
double[] matrixB = new double[matrixSize * matrixSize];
// Initialize matrices with random values
Random random = new Random();
for (int i = 0; i < matrixA.length; i++) {
matrixA[i] = random.nextDouble();
matrixB[i] = random.nextDouble();
}
benchmark.benchmark("Matrix Multiplication - C",
() -> benchmark.matrixMultiplyC(matrixA, matrixB, matrixSize), 10);
benchmark.benchmark("Matrix Multiplication - Java",
() -> benchmark.matrixMultiplyJava(matrixA, matrixB, matrixSize), 10);
// Sorting benchmark
int arraySize = 10000;
int[] unsortedArray = new int[arraySize];
for (int i = 0; i < arraySize; i++) {
unsortedArray[i] = random.nextInt(10000);
}
benchmark.benchmark("QuickSort - C",
() -> benchmark.quickSortC(unsortedArray), 100);
benchmark.benchmark("QuickSort - Java",
() -> benchmark.quickSortJava(unsortedArray), 100);
// Fibonacci benchmark
int fibNumber = 40;
benchmark.benchmark("Fibonacci - C",
() -> benchmark.fibonacciC(fibNumber), 1000);
benchmark.benchmark("Fibonacci - Java",
() -> benchmark.fibonacciJava(fibNumber), 1000);
} finally {
benchmark.close();
}
}
}
Advanced Use Cases
Scientific Computing Integration
scientific_computing.c
#include <math.h>
#include <stdio.h>
// Numerical integration using Simpson's rule
double integrate_simpson(double (*f)(double), double a, double b, int n) {
if (n % 2 != 0) n++; // Ensure n is even
double h = (b - a) / n;
double sum = f(a) + f(b);
for (int i = 1; i < n; i++) {
double x = a + i * h;
if (i % 2 == 0) {
sum += 2 * f(x);
} else {
sum += 4 * f(x);
}
}
return sum * h / 3.0;
}
// Differential equation solver (Euler's method)
void solve_ode_euler(double (*f)(double, double), double y0, double t0, double t_end,
int steps, double* t_out, double* y_out) {
double h = (t_end - t0) / steps;
double t = t0;
double y = y0;
for (int i = 0; i <= steps; i++) {
t_out[i] = t;
y_out[i] = y;
y += h * f(t, y);
t += h;
}
}
// Fast Fourier Transform (simplified)
void fft(double* real, double* imag, int n) {
if (n <= 1) return;
// Split into even and odd
double even_real[n/2], even_imag[n/2];
double odd_real[n/2], odd_imag[n/2];
for (int i = 0; i < n/2; i++) {
even_real[i] = real[2*i];
even_imag[i] = imag[2*i];
odd_real[i] = real[2*i + 1];
odd_imag[i] = imag[2*i + 1];
}
// Recursive FFT
fft(even_real, even_imag, n/2);
fft(odd_real, odd_imag, n/2);
// Combine
for (int k = 0; k < n/2; k++) {
double angle = -2 * M_PI * k / n;
double cos_angle = cos(angle);
double sin_angle = sin(angle);
double t_real = cos_angle * odd_real[k] - sin_angle * odd_imag[k];
double t_imag = cos_angle * odd_imag[k] + sin_angle * odd_real[k];
real[k] = even_real[k] + t_real;
imag[k] = even_imag[k] + t_imag;
real[k + n/2] = even_real[k] - t_real;
imag[k + n/2] = even_imag[k] - t_imag;
}
}
ScientificComputing.java
import org.graalvm.polyglot.*;
import java.nio.file.*;
import java.util.function.Function;
public class ScientificComputing {
private final Context context;
private final Value cLibrary;
public ScientificComputing() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.build();
Path bitcodeFile = compileCToBitcode(Paths.get("scientific_computing.c"));
this.cLibrary = context.eval(Source.newBuilder("llvm", bitcodeFile.toFile()).build());
}
private Path compileCToBitcode(Path cSource) {
try {
Path bitcodeFile = Files.createTempFile("scientific", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm", "-O3", "-lm",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
process.waitFor();
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Compilation failed", e);
}
}
public double integrate(Function<Double, Double> function, double a, double b, int n) {
Value integrateFunction = cLibrary.getMember("integrate_simpson");
// Create C function pointer from Java function
Value cFunction = Value.asValue(new CFunction() {
@Override
public double execute(Value... args) {
double x = args[0].asDouble();
return function.apply(x);
}
});
return integrateFunction.execute(cFunction, a, b, n).asDouble();
}
public ODESolution solveODE(ODEFunction function, double y0, double t0, double tEnd, int steps) {
Value solveFunction = cLibrary.getMember("solve_ode_euler");
Value cFunction = Value.asValue(new ODE_CFunction() {
@Override
public double execute(Value... args) {
double t = args[0].asDouble();
double y = args[1].asDouble();
return function.compute(t, y);
}
});
double[] tOut = new double[steps + 1];
double[] yOut = new double[steps + 1];
Value tArray = context.asValue(tOut);
Value yArray = context.asValue(yOut);
solveFunction.execute(cFunction, y0, t0, tEnd, steps, tArray, yArray);
return new ODESolution(tOut, yOut);
}
public FFTResult fft(double[] real, double[] imag) {
double[] realCopy = real.clone();
double[] imagCopy = imag.clone();
Value realArray = context.asValue(realCopy);
Value imagArray = context.asValue(imagCopy);
Value fftFunction = cLibrary.getMember("fft");
fftFunction.execute(realArray, imagArray, realCopy.length);
return new FFTResult(realCopy, imagCopy);
}
// Functional interfaces
@FunctionalInterface
public interface CFunction {
double execute(Value... args);
}
@FunctionalInterface
public interface ODE_CFunction {
double execute(Value... args);
}
@FunctionalInterface
public interface ODEFunction {
double compute(double t, double y);
}
// Result classes
public static class ODESolution {
public final double[] time;
public final double[] values;
public ODESolution(double[] time, double[] values) {
this.time = time;
this.values = values;
}
public void print() {
for (int i = 0; i < time.length; i++) {
System.out.printf("t=%.2f, y=%.4f%n", time[i], values[i]);
}
}
}
public static class FFTResult {
public final double[] real;
public final double[] imag;
public FFTResult(double[] real, double[] imag) {
this.real = real;
this.imag = imag;
}
public double[] getMagnitude() {
double[] magnitude = new double[real.length];
for (int i = 0; i < real.length; i++) {
magnitude[i] = Math.sqrt(real[i] * real[i] + imag[i] * imag[i]);
}
return magnitude;
}
}
public void close() {
context.close();
}
public static void main(String[] args) {
ScientificComputing sciComp = new ScientificComputing();
try {
// Test numerical integration
System.out.println("=== Numerical Integration ===");
Function<Double, Double> sinFunction = Math::sin;
double integral = sciComp.integrate(sinFunction, 0, Math.PI, 1000);
System.out.printf("∫sin(x)dx from 0 to π = %.6f (expected: 2.0)%n", integral);
// Test ODE solving
System.out.println("\n=== ODE Solving ===");
ODEFunction exponentialODE = (t, y) -> y; // dy/dt = y
ODESolution solution = sciComp.solveODE(exponentialODE, 1.0, 0.0, 2.0, 10);
System.out.println("Exponential growth solution:");
solution.print();
// Test FFT
System.out.println("\n=== FFT ===");
double[] signalReal = {1, 1, 1, 1, 0, 0, 0, 0};
double[] signalImag = new double[signalReal.length];
FFTResult fftResult = sciComp.fft(signalReal, signalImag);
double[] magnitude = fftResult.getMagnitude();
System.out.println("FFT Magnitude:");
for (int i = 0; i < magnitude.length; i++) {
System.out.printf("Frequency bin %d: %.3f%n", i, magnitude[i]);
}
} finally {
sciComp.close();
}
}
}
Troubleshooting & Best Practices
Error Handling and Debugging
import org.graalvm.polyglot.*;
import java.nio.file.*;
import java.util.*;
public class SulongBestPractices {
public static class SafeSulongExecutor {
private final Context context;
public SafeSulongExecutor() {
this.context = Context.newBuilder()
.allowAllAccess(true)
.option("llvm.verbose", "false")
.option("llvm.traceExecution", "false")
.build();
}
public ExecutionResult executeBitcode(Path bitcodeFile, String functionName, Object... args) {
try {
// Load and validate bitcode
if (!Files.exists(bitcodeFile)) {
return ExecutionResult.error("Bitcode file not found: " + bitcodeFile);
}
Source source = Source.newBuilder("llvm", bitcodeFile.toFile()).build();
Value library = context.eval(source);
// Check if function exists
if (!library.hasMember(functionName)) {
return ExecutionResult.error("Function not found: " + functionName);
}
Value function = library.getMember(functionName);
// Validate function can be executed
if (!function.canExecute()) {
return ExecutionResult.error("Function is not executable: " + functionName);
}
// Execute function with timeout
long startTime = System.nanoTime();
Value result = function.execute(args);
long executionTime = System.nanoTime() - startTime;
return ExecutionResult.success(result, executionTime);
} catch (PolyglotException e) {
return ExecutionResult.error("Execution failed: " + e.getMessage(), e);
} catch (Exception e) {
return ExecutionResult.error("Unexpected error: " + e.getMessage(), e);
}
}
public void close() {
context.close();
}
}
public static class ExecutionResult {
private final boolean success;
private final Value result;
private final long executionTimeNs;
private final String errorMessage;
private final Exception exception;
private ExecutionResult(boolean success, Value result, long executionTimeNs,
String errorMessage, Exception exception) {
this.success = success;
this.result = result;
this.executionTimeNs = executionTimeNs;
this.errorMessage = errorMessage;
this.exception = exception;
}
public static ExecutionResult success(Value result, long executionTimeNs) {
return new ExecutionResult(true, result, executionTimeNs, null, null);
}
public static ExecutionResult error(String errorMessage) {
return new ExecutionResult(false, null, 0, errorMessage, null);
}
public static ExecutionResult error(String errorMessage, Exception exception) {
return new ExecutionResult(false, null, 0, errorMessage, exception);
}
public boolean isSuccess() { return success; }
public Value getResult() { return result; }
public long getExecutionTimeMs() { return executionTimeNs / 1_000_000; }
public String getErrorMessage() { return errorMessage; }
public Exception getException() { return exception; }
public <T> T getResultAs(Class<T> type) {
if (!success || result == null) {
throw new IllegalStateException("No result available");
}
return result.as(type);
}
}
// Best practices for resource management
public static class ResourceManager implements AutoCloseable {
private final List<AutoCloseable> resources;
private final SafeSulongExecutor executor;
public ResourceManager() {
this.resources = new ArrayList<>();
this.executor = new SafeSulongExecutor();
resources.add(executor);
}
public <T extends AutoCloseable> T manage(T resource) {
resources.add(resource);
return resource;
}
public ExecutionResult executeSafe(Path bitcodeFile, String functionName, Object... args) {
return executor.executeBitcode(bitcodeFile, functionName, args);
}
@Override
public void close() {
// Close resources in reverse order
Collections.reverse(resources);
for (AutoCloseable resource : resources) {
try {
resource.close();
} catch (Exception e) {
System.err.println("Error closing resource: " + e.getMessage());
}
}
}
}
// Performance monitoring
public static class PerformanceMonitor {
private final Map<String, List<Long>> executionTimes;
public PerformanceMonitor() {
this.executionTimes = new HashMap<>();
}
public void recordExecution(String functionName, long durationNs) {
executionTimes.computeIfAbsent(functionName, k -> new ArrayList<>())
.add(durationNs);
}
public void printStatistics() {
System.out.println("\n=== Performance Statistics ===");
for (Map.Entry<String, List<Long>> entry : executionTimes.entrySet()) {
String functionName = entry.getKey();
List<Long> times = entry.getValue();
LongSummaryStatistics stats = times.stream()
.mapToLong(Long::longValue)
.map(t -> t / 1_000_000) // Convert to milliseconds
.summaryStatistics();
System.out.printf("%s: count=%d, avg=%.2fms, min=%dms, max=%dms%n",
functionName, stats.getCount(), stats.getAverage(),
stats.getMin(), stats.getMax());
}
}
}
public static void main(String[] args) {
try (ResourceManager manager = new ResourceManager();
PerformanceMonitor monitor = new PerformanceMonitor()) {
// Example usage with error handling and monitoring
Path testBitcode = compileTestBitcode();
// Test multiple executions with monitoring
for (int i = 0; i < 5; i++) {
ExecutionResult result = manager.executeSafe(testBitcode, "add", 10, 20);
if (result.isSuccess()) {
int sum = result.getResultAs(Integer.class);
monitor.recordExecution("add", result.getExecutionTimeMs() * 1_000_000);
System.out.printf("Execution %d: 10 + 20 = %d (took %d ms)%n",
i + 1, sum, result.getExecutionTimeMs());
} else {
System.err.println("Execution failed: " + result.getErrorMessage());
}
}
// Print performance statistics
monitor.printStatistics();
} catch (Exception e) {
System.err.println("Application error: " + e.getMessage());
e.printStackTrace();
}
}
private static Path compileTestBitcode() {
try {
Path cSource = Files.createTempFile("test", ".c");
Files.write(cSource, """
int add(int a, int b) {
return a + b;
}
""".getBytes());
Path bitcodeFile = Files.createTempFile("test", ".bc");
ProcessBuilder pb = new ProcessBuilder(
"clang", "-c", "-emit-llvm", "-O2",
"-o", bitcodeFile.toString(),
cSource.toString()
);
Process process = pb.start();
process.waitFor();
Files.deleteIfExists(cSource);
return bitcodeFile;
} catch (Exception e) {
throw new RuntimeException("Failed to create test bitcode", e);
}
}
}
Summary
Sulong provides powerful capabilities for executing C/C++ code within the JVM ecosystem:
- Seamless Integration: Direct execution of LLVM bitcode in Java applications
- High Performance: Leverages GraalVM's optimizing compiler
- Memory Safety: Automatic memory management with Java interoperability
- Rich Interop: Bidirectional calling between Java and native code
- Production Ready: Error handling, monitoring, and resource management
Key benefits include reduced system complexity, improved security through memory safety, and the ability to leverage existing C/C++ libraries while maintaining Java's productivity and ecosystem advantages.