Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f9d7311
regen: bump meos-idl.json to MEOS-API + regenerate extended types
estebanzimanyi May 21, 2026
6747729
regen: bump to ecosystem-pin-2026-06-11f — base json/jsonb/jsonpath +…
estebanzimanyi Jun 11, 2026
11c124c
test: run each test class in its own fork (reuseForks=false)
estebanzimanyi Jun 11, 2026
a0567e7
refactor(facade): migrate TNumber multiply off the legacy facade (mul…
estebanzimanyi Jun 11, 2026
50bf5f4
refactor(facade): migrate the same-name call-sites off the legacy facade
estebanzimanyi Jun 11, 2026
e7bed5d
refactor(facade): migrate the count-out-param set/spanset families of…
estebanzimanyi Jun 11, 2026
a5121ba
refactor(facade): migrate datespan/datespanset off the legacy facade …
estebanzimanyi Jun 11, 2026
ead38c7
refactor(facade): migrate dateset off the legacy facade (pg_date_* ->…
estebanzimanyi Jun 11, 2026
b295035
refactor(facade): migrate TInt/TFloat/TBool off the legacy facade + f…
estebanzimanyi Jun 12, 2026
9adeee0
regen: advance pin to ecosystem-pin-2026-06-11g — cstring_to_text/tex…
estebanzimanyi Jun 12, 2026
10bdbb6
refactor(facade): migrate TextSet + ConversionUtils off the legacy fa…
estebanzimanyi Jun 12, 2026
7e75de1
refactor(facade): migrate TText off the legacy facade (cstring->text …
estebanzimanyi Jun 12, 2026
c32fecf
refactor(facade): migrate tstzspan/tstzset off the legacy facade
estebanzimanyi Jun 12, 2026
0c43392
refactor(facade): migrate STBox off the legacy facade (geo helpers ->…
estebanzimanyi Jun 12, 2026
20e20eb
feat(codegen): bind by-value struct returns via the sret convention
estebanzimanyi Jun 12, 2026
4a6e5c1
refactor(facade): migrate the split family off the legacy facade
estebanzimanyi Jun 12, 2026
790b119
refactor(facade): migrate TNumber/TPoint/Temporal off the legacy facade
estebanzimanyi Jun 12, 2026
0f43b45
fix: initialize the MEOS collation so text comparison does not crash
estebanzimanyi Jun 12, 2026
e38b78a
refactor(facade): migrate the tests off the legacy facade and delete it
estebanzimanyi Jun 12, 2026
6449eed
regen: advance the MEOS surface to ecosystem-pin-2026-06-12c
estebanzimanyi Jun 12, 2026
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
72,062 changes: 66,836 additions & 5,226 deletions codegen/input/meos-idl.json

Large diffs are not rendered by default.

125 changes: 121 additions & 4 deletions codegen/src/main/java/FunctionsGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public class FunctionsGenerator {
// These are mapped to int in Java (JNR-FFI represents C enums as int).
private final Set<String> enumNames = new HashSet<>();

// Struct names → SysV-AMD64 size in bytes, from the JSON "structs" section.
// Used to detect functions that return a struct BY VALUE. On the SysV /
// AArch64 ABIs a by-value struct larger than 16 bytes is returned via memory
// (the "sret" convention): the caller allocates the struct and passes a hidden
// pointer as an implicit FIRST argument; the callee fills it and returns it in
// the return register. jnr-ffi cannot bind a by-value struct return mapped to
// a bare Pointer (it mis-reads the return register), so for these we model the
// sret pointer explicitly — see generateAllInterfaces / generateStaticMethod.
private final Map<String, Integer> structSizes = new HashMap<>();

// DateADT → typedef int32_t → Java int
// Timestamp → typedef int64_t → Java long (no timezone)
// TimestampTz → typedef int64_t → Java long (with timezone)
Expand Down Expand Up @@ -112,6 +122,10 @@ private void run(String inputPath, String outputPath) throws IOException {
collectEnumNames(root);
System.out.println("Enums collected: " + enumNames);

// 1b. Collect struct layouts so by-value struct returns can be bound
// via the sret calling convention (see structSizes).
collectStructs(root);

// 2. Parse all functions
JsonNode functionsNode = root.get("functions");
if (functionsNode == null || !functionsNode.isArray()) {
Expand Down Expand Up @@ -157,6 +171,69 @@ private void collectEnumNames(JsonNode root) {
}
}

/**
* Collects struct layouts from the JSON "structs" section and records each
* struct's SysV-AMD64 size. Structs larger than 16 bytes are returned via
* memory (sret) when used as a by-value return type; that is the only fact
* the generator needs (see structSizes / parseFunctionDef).
*/
private void collectStructs(JsonNode root) {
JsonNode structs = root.get("structs");
if (structs == null || !structs.isArray()) {
return;
}
List<String> registerReturned = new ArrayList<>();
for (JsonNode s : structs) {
JsonNode nameNode = s.get("name");
JsonNode fields = s.get("fields");
if (nameNode == null || fields == null || !fields.isArray()) {
continue;
}
int off = 0, maxAlign = 1;
for (JsonNode f : fields) {
JsonNode ct = f.get("cType");
int sz = (ct == null) ? 8 : cFieldSize(ct.asText());
off = ((off + sz - 1) / sz) * sz; // align field to its own size
off += sz;
maxAlign = Math.max(maxAlign, sz);
}
int size = ((off + maxAlign - 1) / maxAlign) * maxAlign;
String nm = nameNode.asText();
structSizes.put(nm, size);
if (size <= 16) {
registerReturned.add(nm + "(" + size + "B)");
}
}
System.out.println("Structs collected: " + structSizes.size());
// No silent caps: a by-value return of a <=16B struct is register-returned
// (not sret) and is NOT handled by the sret path below. Log it so the gap
// is visible rather than silently mis-bound.
if (!registerReturned.isEmpty()) {
System.out.println("NOTE: register-returned structs (<=16B, NOT sret-bound): "
+ registerReturned);
}
}

/** SysV-AMD64 size of a single C field type (pointers and 8-byte scalars = 8). */
private int cFieldSize(String cType) {
String t = cType.replace("const ", "").trim();
if (t.endsWith("*")) {
return 8;
}
return switch (t) {
case "double", "float8", "long", "int64", "int64_t", "uint64",
"uint64_t", "size_t", "uintptr_t", "Datum",
"Timestamp", "TimestampTz" -> 8;
case "int", "int32", "int32_t", "uint32",
"uint32_t", "float", "DateADT" -> 4;
case "short", "int16", "int16_t",
"uint16", "uint16_t" -> 2;
case "bool", "char", "int8", "int8_t",
"uint8", "uint8_t" -> 1;
default -> 8; // nested struct/pointer: conservative
};
}

private FunctionDef parseFunctionDef(JsonNode fn) {
String name = fn.get("name").asText();

Expand Down Expand Up @@ -198,7 +275,16 @@ private FunctionDef parseFunctionDef(JsonNode fn) {
}
}

return new FunctionDef(name, retJava, retCType, params);
// By-value struct return: if the return C type names a struct larger than
// 16 bytes, it is returned via memory (sret) and must be bound with an
// explicit hidden first pointer argument (see generateStaticMethod).
int sretStructSize = 0;
Integer sz = structSizes.get(retCType.replace("struct ", "").trim());
if (sz != null && sz > 16) {
sretStructSize = sz;
}

return new FunctionDef(name, retJava, retCType, params, sretStructSize);
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -476,10 +562,17 @@ private String generateAllInterfaces(List<FunctionDef> functions, int partSize)
sb.append("\tpublic interface MeosLibraryPart").append(letters[p]).append(" {\n\n");
for (int i = start; i < end; i++) {
FunctionDef fn = functions.get(i);
String ifaceParams = buildInterfaceParamList(fn.params);
// sret return: the struct pointer is a hidden FIRST argument.
if (fn.sretStructSize > 0) {
ifaceParams = ifaceParams.isEmpty()
? "Pointer _sret"
: "Pointer _sret, " + ifaceParams;
}
sb.append("\t\t")
.append(fn.returnType).append(" ")
.append(fn.name).append("(")
.append(buildInterfaceParamList(fn.params))
.append(ifaceParams)
.append(");\n\n");
}
sb.append("\t}\n\n");
Expand Down Expand Up @@ -554,6 +647,12 @@ private String generateStaticMethod(FunctionDef fn, int partIndex) {
OUTPUT_RESULT_PARAMS.contains(p.name)
&& p.javaType().equals("Pointer"));

// sret pattern: the function returns a >16B struct by value. The wrapper
// allocates the struct buffer internally, passes it as the hidden first
// argument, and returns the filled buffer. Callers read the struct fields
// off the returned Pointer (.getPointer(0), .getInt(offset), …).
boolean isSretStruct = fn.sretStructSize > 0;

// Resolve the ResultStrategy from the C type of the result param.
// Determines allocation size, read expression, and wrapper return type.
// Previously hardcoded to Pointer/Long.BYTES/getPointer(0), which caused
Expand Down Expand Up @@ -610,7 +709,7 @@ private String generateStaticMethod(FunctionDef fn, int partIndex) {

// --- Internal allocations ---
// Determine if we need a Runtime (needed for any Memory.allocateDirect call).
boolean needsRuntime = !internalSizeParams.isEmpty() || hasInternalResult;
boolean needsRuntime = !internalSizeParams.isEmpty() || hasInternalResult || isSretStruct;

if (isBoolResultPattern) {
// Emit the "boolean out" sentinel variable first,
Expand All @@ -630,6 +729,12 @@ private String generateStaticMethod(FunctionDef fn, int partIndex) {
.append(resultStrategy.allocExpr()).append(");\n");
}

// Allocate the hidden sret struct buffer.
if (isSretStruct) {
sb.append("\t\tPointer _sret = Memory.allocateDirect(runtime, ")
.append(fn.sretStructSize).append(");\n");
}

// Allocate hidden size_out pointer(s).
for (String paramName : internalSizeParams) {
sb.append("\t\tPointer ").append(paramName)
Expand All @@ -653,6 +758,9 @@ private String generateStaticMethod(FunctionDef fn, int partIndex) {
// --- Build argument list for the interface call ---
// Hidden params (size_out, result) are still forwarded by their local name.
StringJoiner args = new StringJoiner(", ");
if (isSretStruct) {
args.add("_sret"); // hidden sret buffer is the first native argument
}
for (ParamDef p : fn.params) {
if (OUTPUT_SIZE_PARAMS.contains(p.name)
|| (isBoolResultPattern && OUTPUT_RESULT_PARAMS.contains(p.name)
Expand Down Expand Up @@ -687,6 +795,12 @@ private String generateStaticMethod(FunctionDef fn, int partIndex) {
sb.append("\t\tMeosErrorHandler.checkError();\n");
sb.append("\t\treturn out ? result : null;\n");
}
} else if (isSretStruct) {
// The native function fills _sret and also returns it in the return
// register; we keep our own buffer (known size) and return that.
sb.append("\t\t").append(call).append("\n");
sb.append("\t\tMeosErrorHandler.checkError();\n");
sb.append("\t\treturn _sret;\n");
} else if (fn.returnType.equals("void")) {
sb.append("\t\t").append(call).append("\n");
sb.append("\t\tMeosErrorHandler.checkError();\n");
Expand Down Expand Up @@ -743,7 +857,10 @@ private String buildWrapperParamList(List<WrapperParam> params) {

// retCType field so generateStaticMethod can decide the
// wrapper return type independently of the interface return type.
private record FunctionDef(String name, String returnType, String retCType, List<ParamDef> params) {}
// sretStructSize > 0 marks a by-value struct return bound via the sret
// convention (hidden first pointer arg of that many bytes); 0 otherwise.
private record FunctionDef(String name, String returnType, String retCType,
List<ParamDef> params, int sretStructSize) {}

// Added cType field so each param's original C type is
// available when generating conversion code in the static wrapper.
Expand Down
Binary file added jar/JMEOS.jar
Binary file not shown.
7 changes: 4 additions & 3 deletions jmeos-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.4.0</version>
<configuration>
<!-- <forkCount>4</forkCount>-->
<!-- <reuseForks>true</reuseForks>-->
<!-- <argLine>-Xmx4096m -XX:MaxMetaspaceSize=512m</argLine>-->
<!-- MEOS keeps process-global state: meos_initialize cannot be
re-run after a meos_finalize in the same JVM. Give each test
class its own fork so the native MEOS lifecycle is clean. -->
<reuseForks>false</reuseForks>
<skipTests>false</skipTests>
</configuration>
</plugin>
Expand Down
Loading
Loading