diff --git a/codegen/src/main/java/ObjectLayerGenerator.java b/codegen/src/main/java/ObjectLayerGenerator.java index b8f09784a..78e4f11c4 100644 --- a/codegen/src/main/java/ObjectLayerGenerator.java +++ b/codegen/src/main/java/ObjectLayerGenerator.java @@ -70,6 +70,9 @@ private record ClassSpec(String objectKey, String interfaceName, String superInt private List matchFields; private int matchStride; + /** Size in bytes of the {@code TBox} struct, the stride of a {@code TBox *} array. */ + private int tboxStride; + // ------------------------------------------------------------------------- // Entry point // ------------------------------------------------------------------------- @@ -102,6 +105,7 @@ private void init(JsonNode root) { spanSize = structSize(root, "Span"); matchFields = structFields(root, "Match"); matchStride = structSize(root, "Match"); + tboxStride = structSize(root, "TBox"); } private void run(ClassSpec spec, JsonNode root, Path out, String inputPath) throws IOException { @@ -174,23 +178,51 @@ private static int structSize(JsonNode root, String name) { int offset = 0; int widest = 1; for (JsonNode f : structOf(root, name).path("fields")) { - String cType = f.path("cType").asText().trim(); - int bracket = cType.indexOf('['); - int size; - int align; - if (bracket >= 0) { // a fixed-size array field, e.g. char[4] - int count = Integer.parseInt(cType.substring(bracket + 1, cType.indexOf(']')).trim()); - align = scalarBytes(cType.substring(0, bracket).trim()); - size = count * align; - } else { - size = align = scalarBytes(cType); - } - widest = Math.max(widest, align); - offset = (offset + align - 1) / align * align + size; + int[] sa = sizeAlign(root, f.path("cType").asText().trim()); + widest = Math.max(widest, sa[1]); + offset = (offset + sa[1] - 1) / sa[1] * sa[1] + sa[0]; } return (offset + widest - 1) / widest * widest; } + /** The alignment in bytes of a catalog struct: the widest alignment among its fields. */ + private static int structAlign(JsonNode root, String name) { + int widest = 1; + for (JsonNode f : structOf(root, name).path("fields")) { + widest = Math.max(widest, sizeAlign(root, f.path("cType").asText().trim())[1]); + } + return widest; + } + + /** + * The {@code {size, alignment}} in bytes of a struct field's C type: a scalar, a fixed-size array, or + * a nested catalog struct (a TBox nests two Spans), resolved recursively. + */ + private static int[] sizeAlign(JsonNode root, String cType) { + cType = cType.replace("const ", "").trim(); + int bracket = cType.indexOf('['); + if (bracket >= 0) { // a fixed-size array field, e.g. char[4] + int[] base = sizeAlign(root, cType.substring(0, bracket).trim()); + int count = Integer.parseInt(cType.substring(bracket + 1, cType.indexOf(']')).trim()); + return new int[]{base[0] * count, base[1]}; + } + if (isStruct(root, cType)) { + return new int[]{structSize(root, cType), structAlign(root, cType)}; + } + int size = scalarBytes(cType); + return new int[]{size, size}; + } + + /** Whether the catalog defines a struct of this name. */ + private static boolean isStruct(JsonNode root, String name) { + for (JsonNode s : root.path("structs")) { + if (s.path("name").asText().equals(name)) { + return true; + } + } + return false; + } + /** * The scalar fields of a catalog struct with their byte offsets, for folding an array of that struct * into a record. A fixed-size array field (padding) contributes to the offsets but is not a record @@ -368,6 +400,7 @@ && cleanType(fn.path("returnType").path("c").asText()).equals("Temporal *")) { String arrayKind = arrayElement != null ? "objectArray" : retC.equals("TimestampTz *") ? "scalarArray" : retC.equals("Span *") ? "spanArray" + : retC.equals("TBox *") ? "tboxArray" : retC.equals("Match *") ? "matchArray" : null; int last = params.size() - 1; @@ -390,6 +423,7 @@ && cleanType(fn.path("returnType").path("c").asText()).equals("Temporal *")) { case "objectArray" -> "java.util.List"; case "scalarArray" -> "java.util.List"; case "spanArray" -> "java.util.List"; + case "tboxArray" -> "java.util.List"; default -> "java.util.List"; }; return new Method(ooName, fnName, rt, arrayKind, arrayElement, foldArgs); @@ -774,6 +808,8 @@ private String generateMethod(Method m) { + "_array.getLong((long) _i * Long.BYTES))"); case "spanArray" -> arrayFoldBody(m, "new types.collections.time.tstzspan(" + "GeneratedFunctions.span_copy(_array.slice((long) _i * " + spanSize + ")))"); + case "tboxArray" -> arrayFoldBody(m, "new types.boxes.TBox(" + + "GeneratedFunctions.tbox_copy(_array.slice((long) _i * " + tboxStride + ")))"); case "matchArray" -> arrayFoldBody(m, matchElementExpr()); case "split" -> splitFoldBody(m); default -> "\t\treturn " + invocation + ";\n"; diff --git a/jmeos-core/src/test/java/types/temporal/generated/GeneratedTNumberParityTest.java b/jmeos-core/src/test/java/types/temporal/generated/GeneratedTNumberParityTest.java index 68a0c7060..3979babe8 100644 --- a/jmeos-core/src/test/java/types/temporal/generated/GeneratedTNumberParityTest.java +++ b/jmeos-core/src/test/java/types/temporal/generated/GeneratedTNumberParityTest.java @@ -1,7 +1,9 @@ package types.temporal.generated; import functions.GeneratedFunctions; +import jnr.ffi.Memory; import jnr.ffi.Pointer; +import jnr.ffi.Runtime; import org.junit.jupiter.api.Test; import types.basic.tfloat.TFloatSeq; import types.boxes.TBox; @@ -10,6 +12,8 @@ import types.temporal.Temporal; import types.temporal.TemporalType; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -82,4 +86,32 @@ void boxAndValueSpanRestrictionsMatchTheLibrary() { assertEquals(id(GeneratedFunctions.tnumber_minus_spanset(p, spanset.get_inner())), id(g.minusSpanset(spanset))); } + + @Test + void tboxArraysMatchTheLibrary() { + TFloatSeq a = new TFloatSeq("[1@2019-09-01, 3@2019-09-02, 2@2019-09-03]"); + Pointer p = a.getInner(); + GeneratedTNumber g = gen(a); + Runtime rt = Runtime.getSystemRuntime(); + int tboxBytes = 56; // sizeof(TBox) on the 64-bit targets: two 24-byte Spans plus a padded int16 + + // tboxes() folds the contiguous TBox array into TBox wrappers; compare each to the raw element. + Pointer c = Memory.allocate(rt, Integer.BYTES); + Pointer arr = GeneratedFunctions.tnumber_tboxes(p, c); + List boxes = g.tboxes(); + assertEquals(c.getInt(0), boxes.size()); + for (int i = 0; i < boxes.size(); i++) { + assertEquals(GeneratedFunctions.tbox_out(arr.slice((long) i * tboxBytes), 15), + GeneratedFunctions.tbox_out(boxes.get(i).get_inner(), 15)); + } + + // The split-into-N and split-each-N variants fold the same way. + Pointer c2 = Memory.allocate(rt, Integer.BYTES); + GeneratedFunctions.tnumber_split_n_tboxes(p, 2, c2); + assertEquals(c2.getInt(0), g.splitNTboxes(2).size()); + + Pointer c3 = Memory.allocate(rt, Integer.BYTES); + GeneratedFunctions.tnumber_split_each_n_tboxes(p, 2, c3); + assertEquals(c3.getInt(0), g.splitEachNTboxes(2).size()); + } }