Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ jobs:
- name: Install JSON-C
run: sudo apt install libjson-c-dev

# Install GSL (required by MobilityDB skiplists since find_package(GSL REQUIRED))
- name: Install GSL
run: sudo apt-get install -y libgsl-dev

# Fetch and install MEOS library
- name: Fetch MEOS sources
run: git clone https://github.com/MobilityDB/MobilityDB.git
Expand Down
Binary file modified jar/JMEOS.jar
Binary file not shown.
149 changes: 132 additions & 17 deletions src/main/java/builder/FunctionsGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,25 @@
* @since 27/06/2023
*/
public class FunctionsGenerator {


/**
* Number of {@code MeosLibraryPart<N>} sub-interfaces the monolithic
* binding surface is partitioned into. The single big JNR-FFI interface
* makes the JVM build a dynamic proxy whose {@code <clinit>} exceeds the
* 64&nbsp;KB method bytecode limit on ARM64/macOS/Windows and throws
* {@code MethodTooLargeException}. Splitting the surface into a few
* smaller sub-interfaces keeps each generated proxy under the limit.
*/
private static final int NUM_PARTS = 4;

/**
* Deterministic function-name to sub-interface part mapping, built by
* {@link #buildPartAssignment(StringBuilder)}. Pure function of the
* sorted unique function-name surface, so re-running the generator
* reproduces the exact same partition.
*/
private Map<String, Integer> partAssignment = new HashMap<>();

/**
* Path of the file generated by {@link FunctionsExtractor}. Contains a list of functions signature.
*/
Expand Down Expand Up @@ -96,6 +114,11 @@ public static void main(String[] args) throws URISyntaxException {
/* Generation of all the functions signature */
StringBuilder functionsInterfaceBuilder = generator.generateFunctions(generator.C_functionsPath.toString(), false);
StringBuilder functionsClassBuilder = generator.generateFunctions(generator.C_functionsPath.toString(), true);

/* Deterministic partition of the binding surface across NUM_PARTS
* sub-interfaces. Built before the class body so wrapper dispatch
* targets the correct MeosLibraryPart<N>. */
generator.buildPartAssignment(functionsInterfaceBuilder);
System.out.println("Unsupported types: " + generator.unsupportedEquivalentTypes);
System.out.println("Unsupported conversion typedefs: " + generator.unsupportedConversionTypedefs);
System.out.println("Unsupported conversion types: " + generator.unsupportedConversionTypes);
Expand Down Expand Up @@ -300,6 +323,10 @@ public static int countOccurrences(String text, String pattern) {
private List<String> generateReturnProcess(String signature, List<Pair<String, String>> typesNamesList) {
List<String> functionCallingProcess = new ArrayList<>();
List<String> paramNames = BuilderUtils.extractParamNames(signature);
/* Wrapper dispatch target: MeosLibraryPart<N>.meos. (replaces the
* former single MeosLibrary.meos.). Public wrapper signatures are
* unchanged; only the sub-interface they delegate to changes. */
String dispatch = this.dispatchPrefix(BuilderUtils.extractFunctionName(signature)) + ".";

/* Manage the calling of meos library associate function */
if (!typesNamesList.isEmpty()) {
Expand All @@ -311,7 +338,7 @@ private List<String> generateReturnProcess(String signature, List<Pair<String, S
}
}
if (! (paramNames.contains("result") || paramNames.contains("size_out"))){
functionCallingProcess.add("MeosLibrary.meos." + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add(dispatch + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
}

/* Manage the return process : if there is something to return */
Expand Down Expand Up @@ -357,36 +384,36 @@ private List<String> generateReturnProcess(String signature, List<Pair<String, S

if (pointerCount > 1){
functionCallingProcess.add("Pointer result = Memory.allocateDirect(runtime, Long.BYTES);");
functionCallingProcess.add("out = MeosLibrary.meos." + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("out = " + dispatch + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("Pointer new_result = result.getPointer(0);");
functionCallingProcess.add("return out ? new_result : null ;");
}
else if (signature.contains("int ")){
functionCallingProcess.add("Pointer result = Memory.allocateDirect(runtime, Integer.BYTES);");
functionCallingProcess.add("out = MeosLibrary.meos." + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("out = " + dispatch + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("return out ? result.getInt(0) : null ;");
}
else if (signature.contains("double ")){
functionCallingProcess.add("Pointer result = Memory.allocateDirect(runtime, Double.BYTES);");
functionCallingProcess.add("out = MeosLibrary.meos." + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("out = " + dispatch + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("return out ? result.getDouble(0) : null ;");
}
else if (signature.contains("long ")){
functionCallingProcess.add("Pointer result = Memory.allocateDirect(runtime, Long.BYTES);");
functionCallingProcess.add("out = MeosLibrary.meos." + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("out = " + dispatch + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("return out ? result.getLong(0) : null ;");
}
else if (signature.contains("boolean ")){
functionCallingProcess.add("Pointer result = Memory.allocateDirect(runtime, Long.BYTES);");
functionCallingProcess.add("out = MeosLibrary.meos." + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("out = " + dispatch + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("return out ? true : false ;");
}

}
else if (paramNames.contains("size_out")){
functionCallingProcess.add("Runtime runtime = Runtime.getSystemRuntime();");
functionCallingProcess.add("Pointer size_out = Memory.allocateDirect(runtime, Long.BYTES);");
functionCallingProcess.add("return MeosLibrary.meos." + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");
functionCallingProcess.add("return " + dispatch + BuilderUtils.extractFunctionName(signature) + "(" + BuilderUtils.getListWithoutBrackets(paramNames) + ");");

}
else{
Expand Down Expand Up @@ -484,18 +511,106 @@ else if (functionSignature2.contains("as_hexwkb") || functionSignature2.contains
* @return the interface builder
*/
private StringBuilder generateInterface(StringBuilder functionsBuilder) {
if (this.partAssignment.isEmpty()) {
this.buildPartAssignment(functionsBuilder);
}

/* Bucket each signature line by its function's part, preserving the
* source order within a part. */
List<List<String>> partLines = new ArrayList<>();
for (int i = 0; i < NUM_PARTS; i++) {
partLines.add(new ArrayList<>());
}
BuilderUtils.readBuilderLines(functionsBuilder, line -> {
if (!line.isBlank()) {
String name = BuilderUtils.extractFunctionName(line);
if (!name.isBlank()) {
Integer part = this.partAssignment.get(name);
partLines.get(part == null ? 0 : part).add(line);
}
}
});

var builder = new StringBuilder();

builder.append("""
public interface MeosLibrary {
String libraryPath = "libmeos.so";
MeosLibrary INSTANCE = JarLibraryLoader.create(MeosLibrary.class, libraryPath).getLibraryInstance();
MeosLibrary meos = MeosLibrary.INSTANCE;
""");
BuilderUtils.appendStringBuilders(functionsBuilder, builder, "\t", "\n");
builder.append("}");
for (int i = 0; i < NUM_PARTS; i++) {
String iface = partInterfaceName(i);
builder.append("public static interface ").append(iface).append(" {\n");
builder.append("\tString gitLibraryPath= \"/home/runner/work/JMEOS/JMEOS/src/lib\";\n");
builder.append("\tString libraryName= \"meos\";\n");
builder.append('\t').append(iface)
.append(" INSTANCE = JarLibraryLoader.create(").append(iface)
.append(".class, libraryName).getLibraryInstance();\n");
builder.append('\t').append(iface).append(" meos = ").append(iface).append(".INSTANCE;\n");
for (String line : partLines.get(i)) {
builder.append('\t').append(line).append('\n');
}
builder.append("}\n\n");
}
return builder;
}

/**
* Name of the sub-interface a function is bound on.
*
* @param partIndex part index in {@code [0, NUM_PARTS)}
* @return e.g. {@code MeosLibraryPart0}
*/
private static String partInterfaceName(int partIndex) {
return "MeosLibraryPart" + partIndex;
}

/**
* Static dispatch prefix for a wrapper calling {@code name}, e.g.
* {@code MeosLibraryPart3.meos}. The {@code functions} wrappers keep
* their exact public signatures; only the internal target sub-interface
* changes from the former single {@code MeosLibrary.meos}.
*
* @param name function name
* @return dispatch prefix (no trailing dot)
*/
private String dispatchPrefix(String name) {
Integer part = this.partAssignment.get(name);
if (part == null) {
throw new IllegalStateException("No part assigned for function: " + name);
}
return partInterfaceName(part) + ".meos";
}

/**
* Builds the deterministic function-name to sub-interface partition.
* <p>
* The unique function names are sorted and sliced into {@link #NUM_PARTS}
* balanced contiguous buckets ({@code part = floor(i * NUM_PARTS / total)}).
* Because the slice is a pure function of the sorted input header
* surface, re-running the regen reproduces the exact same assignment.
* Overloaded names map to a single part (assignment is keyed by name),
* so all overloads of a function stay on the same sub-interface.
*
* @param functionsBuilder builder of interface signatures
*/
private void buildPartAssignment(StringBuilder functionsBuilder) {
TreeSet<String> names = new TreeSet<>();
BuilderUtils.readBuilderLines(functionsBuilder, line -> {
if (!line.isBlank()) {
String name = BuilderUtils.extractFunctionName(line);
if (!name.isBlank()) {
names.add(name);
}
}
});

List<String> sorted = new ArrayList<>(names);
int total = sorted.size();
Map<String, Integer> assignment = new HashMap<>();
for (int i = 0; i < total; i++) {
int part = (int) ((long) i * NUM_PARTS / Math.max(total, 1));
if (part >= NUM_PARTS) {
part = NUM_PARTS - 1;
}
assignment.put(sorted.get(i), part);
}
this.partAssignment = assignment;
}

/**
* Generation of functions with their conversion types, typedef conversion types and equivalent types.
Expand Down
Loading
Loading