Skip to content

Commit f48806e

Browse files
dfa1claude
andcommitted
refactor: read ZSTD_bounds via the named layout, not magic offsets
Name BOUNDS_LAYOUT's fields (error/lowerBound/upperBound) and derive the read offsets from it with byteOffset(groupElement(...)), so the struct reads track the definition instead of hand-counted 0/8/12. Document how the struct-by-value return allocates through the arena SegmentAllocator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6fc1b37 commit f48806e

2 files changed

Lines changed: 20 additions & 4 deletions

File tree

zstd/src/main/java/io/github/dfa1/zstd/Bindings.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,11 @@ final class Bindings {
158158
NativeLibrary.lookup("ZSTD_getDictID_fromDDict", FunctionDescriptor.of(JAVA_INT, ADDRESS));
159159

160160
// ZSTD_bounds { size_t error; int lowerBound; int upperBound; } — returned by value
161-
private static final MemoryLayout BOUNDS_LAYOUT =
162-
MemoryLayout.structLayout(JAVA_LONG, JAVA_INT, JAVA_INT);
161+
static final MemoryLayout BOUNDS_LAYOUT =
162+
MemoryLayout.structLayout(
163+
JAVA_LONG.withName("error"),
164+
JAVA_INT.withName("lowerBound"),
165+
JAVA_INT.withName("upperBound"));
163166
// ZSTD_bounds ZSTD_cParam_getBounds(ZSTD_cParameter) / ZSTD_dParam_getBounds(ZSTD_dParameter)
164167
static final MethodHandle CPARAM_GET_BOUNDS =
165168
NativeLibrary.lookup("ZSTD_cParam_getBounds", FunctionDescriptor.of(BOUNDS_LAYOUT, JAVA_INT));

zstd/src/main/java/io/github/dfa1/zstd/ZstdBounds.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.dfa1.zstd;
22

33
import java.lang.foreign.Arena;
4+
import java.lang.foreign.MemoryLayout.PathElement;
45
import java.lang.foreign.MemorySegment;
56
import java.lang.foreign.SegmentAllocator;
67
import java.lang.invoke.MethodHandle;
@@ -15,16 +16,28 @@
1516
/// @param upperBound the largest accepted value, inclusive
1617
public record ZstdBounds(int lowerBound, int upperBound) {
1718

19+
// Offsets into the returned ZSTD_bounds struct, derived from the named layout
20+
// rather than hand-counted, so they track the struct definition.
21+
private static final long ERROR_OFFSET = Bindings.BOUNDS_LAYOUT.byteOffset(PathElement.groupElement("error"));
22+
private static final long LOWER_OFFSET = Bindings.BOUNDS_LAYOUT.byteOffset(PathElement.groupElement("lowerBound"));
23+
private static final long UPPER_OFFSET = Bindings.BOUNDS_LAYOUT.byteOffset(PathElement.groupElement("upperBound"));
24+
1825
/// Calls a `*_getBounds` function (which returns a `ZSTD_bounds` struct by
1926
/// value: `{ size_t error; int lowerBound; int upperBound; }`).
2027
static ZstdBounds query(MethodHandle getBounds, int parameter) {
2128
try (Arena arena = Arena.ofConfined()) {
29+
// getBounds returns a ZSTD_bounds struct by value. For a struct return,
30+
// the FFM linker prepends a SegmentAllocator parameter to the handle:
31+
// it allocates BOUNDS_LAYOUT.byteSize() bytes from that allocator, the
32+
// native call writes the struct there, and the handle returns a segment
33+
// viewing it. Passing the arena makes the struct arena-owned (freed on
34+
// close); the cast satisfies invokeExact's exact-type requirement.
2235
MemorySegment bounds = (MemorySegment) getBounds.invokeExact((SegmentAllocator) arena, parameter);
23-
long error = bounds.get(JAVA_LONG, 0);
36+
long error = bounds.get(JAVA_LONG, ERROR_OFFSET);
2437
if (NativeCall.isError(error)) {
2538
throw new ZstdException("parameter has no queryable bounds");
2639
}
27-
return new ZstdBounds(bounds.get(JAVA_INT, 8), bounds.get(JAVA_INT, 12));
40+
return new ZstdBounds(bounds.get(JAVA_INT, LOWER_OFFSET), bounds.get(JAVA_INT, UPPER_OFFSET));
2841
} catch (Throwable t) {
2942
throw NativeCall.rethrow(t);
3043
}

0 commit comments

Comments
 (0)