1- import { ptr , toArrayBuffer } from "bun: ffi"
1+ import { ptr , toArrayBuffer } from "./ ffi.js "
22import type {
3+ Pointer ,
34 PrimitiveType ,
45 PointyObject ,
56 ObjectPointerDef ,
@@ -24,6 +25,7 @@ function fatalError(...args: any[]): never {
2425}
2526
2627export const pointerSize = process . arch === "x64" || process . arch === "arm64" ? 8 : 4
28+ const isBun = typeof process !== "undefined" && "bun" in process . versions
2729
2830const typeSizes : Record < PrimitiveType , number > = {
2931 u8 : 1 ,
@@ -95,6 +97,7 @@ export function allocStruct(structDef: StructDef<any, any>, options?: AllocStruc
9597
9698 const pointer = length > 0 ? ptr ( subBuffer ) : null
9799 pointerPacker ( view , arrayMeta . arrayOffset , pointer )
100+ retainPointerTarget ( buffer , subBuffer )
98101 arrayMeta . lengthPack ( view , arrayMeta . lengthOffset , length )
99102 }
100103
@@ -228,14 +231,18 @@ function primitivePackers(type: PrimitiveType) {
228231 unpack = ( view : DataView , off : number ) => view . getFloat64 ( off , true )
229232 break
230233 case "pointer" :
231- pack = ( view : DataView , off : number , val : bigint | number ) => {
234+ pack = ( view : DataView , off : number , val : Pointer ) => {
232235 pointerSize === 8
233236 ? view . setBigUint64 ( off , val ? BigInt ( val ) : 0n , true )
234237 : view . setUint32 ( off , val ? Number ( val ) : 0 , true )
235238 }
236- unpack = ( view : DataView , off : number ) : number => {
237- const bint = pointerSize === 8 ? view . getBigUint64 ( off , true ) : BigInt ( view . getUint32 ( off , true ) )
238- return Number ( bint )
239+ unpack = ( view : DataView , off : number ) : Pointer => {
240+ if ( pointerSize === 8 ) {
241+ const value = view . getBigUint64 ( off , true )
242+ return isBun ? Number ( value ) : value
243+ }
244+
245+ return view . getUint32 ( off , true )
239246 }
240247 break
241248 default :
@@ -248,6 +255,31 @@ function primitivePackers(type: PrimitiveType) {
248255
249256const { pack : pointerPacker , unpack : pointerUnpacker } = primitivePackers ( "pointer" )
250257
258+ const retainedPointerTargets = new WeakMap < ArrayBufferLike , unknown [ ] > ( )
259+
260+ function retainPointerTarget ( owner : ArrayBufferLike , target : unknown ) {
261+ const retained = retainedPointerTargets . get ( owner )
262+ if ( retained ) {
263+ retained . push ( target )
264+ } else {
265+ retainedPointerTargets . set ( owner , [ target ] )
266+ }
267+ }
268+
269+ function retainIfPointerTargets ( owner : ArrayBufferLike , target : ArrayBufferLike ) {
270+ if ( retainedPointerTargets . has ( target ) ) {
271+ retainPointerTarget ( owner , target )
272+ }
273+ }
274+
275+ function isNullPointer ( pointer : Pointer | null | undefined ) : boolean {
276+ return pointer == null || pointer === 0 || pointer === 0n
277+ }
278+
279+ function toItemCount ( length : number | bigint ) : number {
280+ return typeof length === "bigint" ? Number ( length ) : length
281+ }
282+
251283export function packObjectArray ( val : ( PointyObject | null ) [ ] ) {
252284 const buffer = new ArrayBuffer ( val . length * pointerSize )
253285 const bufferView = new DataView ( buffer )
@@ -299,8 +331,15 @@ export function defineStruct<const Fields extends readonly StructField[], const
299331 size = pointerSize
300332 align = pointerSize
301333 pack = ( view : DataView , off : number , val : string | null ) => {
302- const bufPtr = val ? ptr ( encoder . encode ( val + "\0" ) ) : null
334+ if ( ! val ) {
335+ pointerPacker ( view , off , null )
336+ return
337+ }
338+
339+ const bytes = encoder . encode ( val + "\0" )
340+ const bufPtr = ptr ( bytes )
303341 pointerPacker ( view , off , bufPtr )
342+ retainPointerTarget ( view . buffer , bytes )
304343 }
305344 unpack = ( view : DataView , off : number ) => {
306345 // TODO: Unpack CString from pointer
@@ -312,8 +351,15 @@ export function defineStruct<const Fields extends readonly StructField[], const
312351 size = pointerSize
313352 align = pointerSize
314353 pack = ( view : DataView , off : number , val : string | null ) => {
315- const bufPtr = val ? ptr ( encoder . encode ( val ) ) : null // No null terminator
354+ if ( ! val ) {
355+ pointerPacker ( view , off , null )
356+ return
357+ }
358+
359+ const bytes = encoder . encode ( val ) // No null terminator
360+ const bufPtr = ptr ( bytes )
316361 pointerPacker ( view , off , bufPtr )
362+ retainPointerTarget ( view . buffer , bytes )
317363 }
318364 // Initial unpack returns pointer; will be replaced if lengthOf field exists
319365 unpack = ( view : DataView , off : number ) => {
@@ -347,6 +393,7 @@ export function defineStruct<const Fields extends readonly StructField[], const
347393 }
348394 const nestedBuf = typeOrStruct . pack ( val , options )
349395 pointerPacker ( view , off , ptr ( nestedBuf ) )
396+ retainPointerTarget ( view . buffer , nestedBuf )
350397 }
351398 unpack = ( view , off ) => {
352399 throw new Error ( "Not implemented yet" )
@@ -360,6 +407,7 @@ export function defineStruct<const Fields extends readonly StructField[], const
360407 const nestedView = new Uint8Array ( nestedBuf )
361408 const dView = new Uint8Array ( view . buffer )
362409 dView . set ( nestedView , off )
410+ retainIfPointerTargets ( view . buffer , nestedBuf )
363411 }
364412 unpack = ( view , off ) => {
365413 const slice = view . buffer . slice ( off , off + size )
@@ -412,6 +460,7 @@ export function defineStruct<const Fields extends readonly StructField[], const
412460 bufferView . setUint32 ( i * arrayElementSize , num , true )
413461 }
414462 pointerPacker ( view , off , ptr ( buffer ) )
463+ retainPointerTarget ( view . buffer , buffer )
415464 }
416465 unpack = null !
417466 needsLengthOf = true
@@ -430,6 +479,7 @@ export function defineStruct<const Fields extends readonly StructField[], const
430479 def . packInto ( val [ i ] , bufferView , i * arrayElementSize , options )
431480 }
432481 pointerPacker ( view , off , ptr ( buffer ) )
482+ retainPointerTarget ( view . buffer , buffer )
433483 }
434484 unpack = ( view , off ) => {
435485 throw new Error ( "Not implemented yet" )
@@ -450,6 +500,7 @@ export function defineStruct<const Fields extends readonly StructField[], const
450500 primitivePack ( bufferView , i * arrayElementSize , val [ i ] )
451501 }
452502 pointerPacker ( view , off , ptr ( buffer ) )
503+ retainPointerTarget ( view . buffer , buffer )
453504 }
454505 unpack = null !
455506 needsLengthOf = true
@@ -464,6 +515,7 @@ export function defineStruct<const Fields extends readonly StructField[], const
464515
465516 const packedView = packObjectArray ( val )
466517 pointerPacker ( view , off , ptr ( packedView . buffer ) )
518+ retainPointerTarget ( view . buffer , packedView . buffer )
467519 }
468520 unpack = ( ) => {
469521 // TODO: implement unpack for class pointers
@@ -583,11 +635,11 @@ export function defineStruct<const Fields extends readonly StructField[], const
583635 const ptrAddress = pointerUnpacker ( view , off )
584636 const length = lengthOfField . unpack ( view , off + relativeOffset )
585637
586- if ( ptrAddress === 0 ) {
638+ if ( isNullPointer ( ptrAddress ) ) {
587639 return null
588640 }
589641
590- const byteLength = typeof length === "bigint" ? Number ( length ) : length
642+ const byteLength = toItemCount ( length )
591643
592644 if ( byteLength === 0 ) {
593645 return ""
@@ -604,19 +656,20 @@ export function defineStruct<const Fields extends readonly StructField[], const
604656 requester . unpack = ( view , off ) => {
605657 const result = [ ]
606658 const length = lengthOfField . unpack ( view , off + relativeOffset )
659+ const itemCount = toItemCount ( length )
607660 const ptrAddress = pointerUnpacker ( view , off )
608661
609- if ( ptrAddress === 0n && length > 0 ) {
662+ if ( isNullPointer ( ptrAddress ) && itemCount > 0 ) {
610663 throw new Error ( `Array field ${ requester . name } has null pointer but length ${ length } .` )
611664 }
612- if ( ptrAddress === 0n || length === 0 ) {
665+ if ( isNullPointer ( ptrAddress ) || itemCount === 0 ) {
613666 return [ ]
614667 }
615668
616- const buffer = toArrayBuffer ( ptrAddress , 0 , length * elemSize )
669+ const buffer = toArrayBuffer ( ptrAddress , 0 , itemCount * elemSize )
617670 const bufferView = new DataView ( buffer )
618671
619- for ( let i = 0 ; i < length ; i ++ ) {
672+ for ( let i = 0 ; i < itemCount ; i ++ ) {
620673 result . push ( primitiveUnpack ( bufferView , i * elemSize ) )
621674 }
622675 return result
@@ -628,19 +681,20 @@ export function defineStruct<const Fields extends readonly StructField[], const
628681 requester . unpack = ( view , off ) => {
629682 const result = [ ]
630683 const length = lengthOfField . unpack ( view , off + relativeOffset )
684+ const itemCount = toItemCount ( length )
631685 const ptrAddress = pointerUnpacker ( view , off )
632686
633- if ( ptrAddress === 0n && length > 0 ) {
687+ if ( isNullPointer ( ptrAddress ) && itemCount > 0 ) {
634688 throw new Error ( `Array field ${ requester . name } has null pointer but length ${ length } .` )
635689 }
636- if ( ptrAddress === 0n || length === 0 ) {
690+ if ( isNullPointer ( ptrAddress ) || itemCount === 0 ) {
637691 return [ ]
638692 }
639693
640- const buffer = toArrayBuffer ( ptrAddress , 0 , length * elemSize )
694+ const buffer = toArrayBuffer ( ptrAddress , 0 , itemCount * elemSize )
641695 const bufferView = new DataView ( buffer )
642696
643- for ( let i = 0 ; i < length ; i ++ ) {
697+ for ( let i = 0 ; i < itemCount ; i ++ ) {
644698 result . push ( def . from ( bufferView . getUint32 ( i * elemSize , true ) ) )
645699 }
646700 return result
0 commit comments