1919import java .io .ByteArrayOutputStream ;
2020import java .lang .foreign .Arena ;
2121import java .lang .foreign .MemorySegment ;
22+ import java .lang .foreign .SegmentAllocator ;
2223import java .lang .foreign .ValueLayout ;
2324import java .nio .charset .StandardCharsets ;
2425import java .util .Collection ;
@@ -40,31 +41,45 @@ final class FfmEncoding {
4041 private FfmEncoding () {}
4142
4243 // -------------------------------------------------------------------------
43- // Native segment allocation (inputs: copy-for-now into the call arena )
44+ // Native segment allocation (inputs: copy-for-now into the call scratch )
4445 // -------------------------------------------------------------------------
4546
4647 /**
4748 * Allocate a native segment holding the given bytes, or {@link MemorySegment#NULL} when {@code
4849 * bytes} is null/empty. The Rust side treats a null ptr / zero len as the empty slice.
4950 */
50- static MemorySegment allocateBytes (Arena arena , byte @ Nullable [] bytes ) {
51+ static MemorySegment allocateBytes (SegmentAllocator alloc , byte @ Nullable [] bytes ) {
5152 if (bytes == null || bytes .length == 0 ) {
5253 return MemorySegment .NULL ;
5354 }
54- MemorySegment seg = arena .allocate (bytes .length );
55+ MemorySegment seg = alloc .allocate (bytes .length );
5556 MemorySegment .copy (bytes , 0 , seg , ValueLayout .JAVA_BYTE , 0 , bytes .length );
5657 return seg ;
5758 }
5859
60+ /**
61+ * Allocate a native segment holding the bytes of {@code slice}, copied straight from the slice's
62+ * buffer (no intermediate heap {@code byte[]}), or {@link MemorySegment#NULL} when empty.
63+ */
64+ static MemorySegment allocateSlice (SegmentAllocator alloc , dev .restate .common .Slice slice ) {
65+ int len = slice .readableBytes ();
66+ if (len == 0 ) {
67+ return MemorySegment .NULL ;
68+ }
69+ MemorySegment seg = alloc .allocate (len );
70+ slice .copyTo (seg .asByteBuffer ());
71+ return seg ;
72+ }
73+
5974 /**
6075 * Allocate a native segment holding the UTF-8 bytes of {@code s}, or {@link MemorySegment#NULL}
6176 * when {@code s} is null. An empty (non-null) string yields a zero-length, non-null segment.
6277 */
63- static MemorySegment allocateUtf8 (Arena arena , @ Nullable String s ) {
78+ static MemorySegment allocateUtf8 (SegmentAllocator alloc , @ Nullable String s ) {
6479 if (s == null ) {
6580 return MemorySegment .NULL ;
6681 }
67- return allocateBytes (arena , s .getBytes (StandardCharsets .UTF_8 ));
82+ return allocateBytes (alloc , s .getBytes (StandardCharsets .UTF_8 ));
6883 }
6984
7085 static long len (MemorySegment seg ) {
@@ -102,6 +117,37 @@ static String takeSliceString(MemorySegment sliceStruct) {
102117 return new String (bytes , StandardCharsets .UTF_8 );
103118 }
104119
120+ /**
121+ * Wraps an owned result {@link Slice} (Rust-allocated; must be released via {@code free_buffer})
122+ * in a {@link MemorySegmentSlice} read zero-copy — no copy-out into a heap {@code byte[]}. The
123+ * native buffer is freed when the returned slice becomes unreachable (GC-tied via {@link
124+ * Arena#ofAuto()}), so it is safe to read later and from another thread (deserialization on a
125+ * different dispatcher, or an async socket write). Returns {@link dev.restate.common.Slice#EMPTY}
126+ * for the null/empty slice.
127+ */
128+ static dev .restate .common .Slice wrapOwnedSlice (MemorySegment sliceStruct ) {
129+ MemorySegment ptr = Slice .ptr (sliceStruct );
130+ long len = Slice .len (sliceStruct );
131+ if (ptr .address () == 0 || len == 0 ) {
132+ return dev .restate .common .Slice .EMPTY ;
133+ }
134+ MemorySegment seg =
135+ ptr .reinterpret (len , Arena .ofAuto (), s -> SharedCoreNative .free_buffer (s , len ));
136+ return new MemorySegmentSlice (seg );
137+ }
138+
139+ /**
140+ * Releases an owned result {@link Slice} we are not consuming (e.g. the unused {@code value}/
141+ * {@code extra} arm of a notification). No-op for the null/empty slice.
142+ */
143+ static void freeSlice (MemorySegment sliceStruct ) {
144+ MemorySegment ptr = Slice .ptr (sliceStruct );
145+ long len = Slice .len (sliceStruct );
146+ if (ptr .address () != 0 && len != 0 ) {
147+ SharedCoreNative .free_buffer (ptr , len );
148+ }
149+ }
150+
105151 private static final byte [] EMPTY_BYTES = new byte [0 ];
106152
107153 // -------------------------------------------------------------------------
@@ -114,54 +160,54 @@ static String takeSliceString(MemorySegment sliceStruct) {
114160 * header-list blob.
115161 */
116162 static MemorySegment buildTarget (
117- Arena arena ,
163+ SegmentAllocator alloc ,
118164 Target target ,
119165 @ Nullable String idempotencyKey ,
120166 @ Nullable Collection <Map .Entry <String , String >> headers ) {
121- MemorySegment t = TargetAbi .allocate (arena );
167+ MemorySegment t = TargetAbi .allocate (alloc );
122168
123- MemorySegment service = allocateUtf8 (arena , target .getService ());
169+ MemorySegment service = allocateUtf8 (alloc , target .getService ());
124170 TargetAbi .service_ptr (t , service );
125171 TargetAbi .service_len (t , len (service ));
126172
127- MemorySegment handler = allocateUtf8 (arena , target .getHandler ());
173+ MemorySegment handler = allocateUtf8 (alloc , target .getHandler ());
128174 TargetAbi .handler_ptr (t , handler );
129175 TargetAbi .handler_len (t , len (handler ));
130176
131- MemorySegment key = allocateUtf8 (arena , target .getKey ());
177+ MemorySegment key = allocateUtf8 (alloc , target .getKey ());
132178 TargetAbi .key_ptr (t , key );
133179 TargetAbi .key_len (t , len (key ));
134180
135- MemorySegment idem = allocateUtf8 (arena , idempotencyKey );
181+ MemorySegment idem = allocateUtf8 (alloc , idempotencyKey );
136182 TargetAbi .idempotency_key_ptr (t , idem );
137183 TargetAbi .idempotency_key_len (t , len (idem ));
138184
139- MemorySegment headersBlob = allocateBytes (arena , encodeHeaderList (headers ));
185+ MemorySegment headersBlob = allocateBytes (alloc , encodeHeaderList (headers ));
140186 TargetAbi .headers_ptr (t , headersBlob );
141187 TargetAbi .headers_len (t , len (headersBlob ));
142188
143189 return t ;
144190 }
145191
146- /** Builds a success-valued {@link NonEmptyValueAbi} struct in {@code arena} . */
147- static MemorySegment buildSuccessValue (Arena arena , byte [] value ) {
148- MemorySegment v = NonEmptyValueAbi .allocate (arena );
192+ /** Builds a success-valued {@link NonEmptyValueAbi} struct, copying {@code value} zero-hop . */
193+ static MemorySegment buildSuccessValue (SegmentAllocator alloc , dev . restate . common . Slice value ) {
194+ MemorySegment v = NonEmptyValueAbi .allocate (alloc );
149195 NonEmptyValueAbi .is_failure (v , 0 );
150- MemorySegment val = allocateBytes ( arena , value );
196+ MemorySegment val = allocateSlice ( alloc , value );
151197 NonEmptyValueAbi .value_ptr (v , val );
152198 NonEmptyValueAbi .value_len (v , len (val ));
153199 NonEmptyValueAbi .failure_ptr (v , MemorySegment .NULL );
154200 NonEmptyValueAbi .failure_len (v , 0L );
155201 return v ;
156202 }
157203
158- /** Builds a failure-valued {@link NonEmptyValueAbi} struct in {@code arena} . */
159- static MemorySegment buildFailureValue (Arena arena , TerminalException failure ) {
160- MemorySegment v = NonEmptyValueAbi .allocate (arena );
204+ /** Builds a failure-valued {@link NonEmptyValueAbi} struct. */
205+ static MemorySegment buildFailureValue (SegmentAllocator alloc , TerminalException failure ) {
206+ MemorySegment v = NonEmptyValueAbi .allocate (alloc );
161207 NonEmptyValueAbi .is_failure (v , 1 );
162208 NonEmptyValueAbi .value_ptr (v , MemorySegment .NULL );
163209 NonEmptyValueAbi .value_len (v , 0L );
164- MemorySegment fail = allocateBytes (arena , encodeFailure (failure ));
210+ MemorySegment fail = allocateBytes (alloc , encodeFailure (failure ));
165211 NonEmptyValueAbi .failure_ptr (v , fail );
166212 NonEmptyValueAbi .failure_len (v , len (fail ));
167213 return v ;
0 commit comments