Change in how C function signatures are translated in Swift 5?

I decided try out building MongoSwift using the newly released Swift 5.0 development snapshots and encountered some interesting behavior. In particular, the Swift interfaces for a number of C functions are different and there are OpaquePointers expected in many places that expect UnsafeMutablePointers in Swift 4.

MongoSwift uses two C libraries under the hood, libbson and libmongoc. To illustrate the change with a particular example, consider the function bson_new.

Its C signature:

bson_t *
bson_new (void);

Printing out type(of: bson_new) in Swift 4.2 gives me
() -> Optional<UnsafeMutablePointer<_bson_t>>.

But with the 5.0 snapshot, I get:
() -> Optional<OpaquePointer>

I haven't had any luck finding any discussion of these changes in Swift evolution proposals or in the forums. Does anyone have any insight into this?

cc @jrose. There wasn't any intentional change here that I know about. Swift will use OpaquePointer to import a pointer to any type Swift can't import, including incomplete forward-declared C structs. What is the definition of bson_t, and is there any chance it changed its definition between libbson library versions? If it was a struct with a definition that became forward-declared, or it was changed to a typedef to some other kind of type Swift's importer doesn't handle, that could explain the change.

2 Likes

Another (somewhat remote) possibility is that bson_t has an alignment greater than 16 for some reason.

2 Likes

Thanks for the quick reply!

In both cases I am using libbson 1.13, so I don't believe versioning is at play here.

Type definition:

BSON_ALIGNED_BEGIN (128)
typedef struct {
   uint32_t flags;       /* Internal flags for the bson_t. */
   uint32_t len;         /* Length of BSON data. */
   uint8_t padding[120]; /* Padding for stack allocation. */
} bson_t BSON_ALIGNED_END (128);

Those alignments are probably meant to be bits rather than bytes. :-( No one (to a near approximation) supports a 128-byte alignment.

EDIT: okay, that's a brash and untrue statement. But Swift doesn't, and BSON probably didn't mean to.

1 Like

Well, sizeof(bson_t) does equal 128, so maybe it was intentional. But I have no idea why it would be intentional.

EDIT: You will be shocked to learn that bson_malloc is implemented as simply calling malloc by default and so does not in fact return 128-byte-aligned memory.

1 Like

So I can understand that this puts you in an awkward position, because the library is doing something a little silly and it's leading to poor import results, but you may not feel empowered to actually do anything about it. As a short-term solution, you can hack your headers; as a longer-term solution, you can bring it up with the libbson maintainers; otherwise you might feel a little stuck.

In the long run, Swift should be capable of importing types like this; we just can't allow them to be abstracted over. That's not something we can express right now, so instead we're doing something conservative but correct, and it's not great.

2 Likes

Thanks for the input, all.

I work with the maintainers, so I'll speak with them tomorrow to try to get some insight into what's going on there.

What specifically changed about type importing in Swift 5 that led to this?

We capped alignment at 16 bytes for ABI stability.

3 Likes

Just an assumption but could this be related to

"\x13" e_name decimal128 128-bit decimal floating point

from the BSON specification?!

As far as I know BSON can hold elements of a Decimal128 type.

Very possibly, but that's still 128 bits, not 128 bytes. (And BSON-as-transmission format also doesn't guarantee alignment, by design.)

1 Like

Face palm, yeah I should have read more carefully. Thank you for pointing that out.