Skip to content

Add additional FFI types and pointer operations#6131

Merged
pchiusano merged 14 commits intotrunkfrom
topic/ffi-additions
Jan 22, 2026
Merged

Add additional FFI types and pointer operations#6131
pchiusano merged 14 commits intotrunkfrom
topic/ffi-additions

Conversation

@dolio
Copy link
Contributor

@dolio dolio commented Jan 21, 2026

This PR fills in some FFI types, and adds the ability to work with raw pointers.

  • Added int8 and uint8 to the FFI type specs.
  • Added many opaque types for sized C-like values, e.g. Nat16. These don't have values themselves, but are used in other types.
  • Added a Ptr type. It's parameterized by its type, and the practically usable parameters are those that correspond to C types.
  • Added many operations for working with pointers. These are almost all at specific types, and the smaller sized unison types automatically convert to Unison types with actual values. E.G. set : Ptr Nat16 -> Nat ->{IO} ()
  • Added the ptr FFI type that allows passing/returning pointers to/from FFI imports.

Note, it's also possible to have pointers to pointers and so on. E.G. there are functions for working with Ptr (Ptr Int32).

I extended the dll-ffi tests to do some operations exercising the pointer ffi. But I haven't exhaustively tested all the pointer operations in any test.

dolio added 4 commits January 20, 2026 16:48
Includes various types that are used as phantoms in pointer types.
E.G. `Ptr Nat32`. This reprsents the actual type of the pointer,
but when reading/writing, operations act in terms of `Nat` with
conversion.
@dolio dolio requested a review from a team as a code owner January 21, 2026 19:09
@dolio dolio force-pushed the topic/ffi-additions branch from a26e4ab to 18dfbce Compare January 21, 2026 20:41
@dolio
Copy link
Contributor Author

dolio commented Jan 21, 2026

Base tests pass.

Copy link
Member

@pchiusano pchiusano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

@aryairani
Copy link
Contributor

@dolio go ahead and merge if you're ready

@dolio
Copy link
Contributor Author

dolio commented Jan 22, 2026

Yeah, I'm making a couple of additions before merging. Paul wanted a way to access byte array contents.

dolio and others added 3 commits January 22, 2026 14:56
To get a pointer to the memory of a byte array, this adds
`PinnedByteArray.contents`. However, since even pinned arrays are garbage
collected, it's not safe to work with these pointers while dropping
references to the actual array.

To rectify this problem, this also adds the `IO.keepAlive` builtin. This is
a function that maintains a reference to a given value (by storing it in
the continuation) while executing a block.

As an incidental change, pointer casting has been given a more direct
implementation that doesn't go through a 'foreign' function.
@dolio
Copy link
Contributor Author

dolio commented Jan 22, 2026

Okay. The new additions are

  1. Pinned.contents : Pinned g -> Ptr Nat8 – gets the pointer to the bytes of a pinned byte array (getting a pointer to a non-pinned array is unsafe because they can move).
  2. IO.keepAlive : a -> '{IO} b ->{IO} b – this is necessary if you want to guarantee that a byte array (a) doesn't get garbage collected while you're working with its corresponding pointer in a loop (second argument).

@pchiusano
Copy link
Member

I don't know if I get the need for the keepAlive function. It seems like if you're willing to call that function, you could also just keep the original pinned byte array around.

Like you could literally just define it in pure Unison and it would just ignore its first argument and force the second:

keepAlive bytes loop = loop()

Or is there more to it than that?

@dolio
Copy link
Contributor Author

dolio commented Jan 22, 2026

That function doesn't work. It tail calls loop, which means that the keepAlive frame no longer exists, and bytes isn't on the stack, except possibly incidentally.

The builtin tail calls loop with a continuation that has a special frame that contains the first argument, so that it stays live until you return back up the stack.

@pchiusano
Copy link
Member

Gotcha. Okay cool. At first I was thinking I could write something like this:

keepAlive keep logic = 
  r = logic()
  _ = 1 + 1
  r

Now logic is no longer a tail call, and keep remains on the stack in the meantime that logic is being forced.

However, I can appreciate that in the future, a fancier Unison runtime optimizer might do something else here that lets keep be garbage collected, so having a primitive for it is better.

@pchiusano pchiusano merged commit 04f4bda into trunk Jan 22, 2026
31 checks passed
@pchiusano pchiusano deleted the topic/ffi-additions branch January 22, 2026 23:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants