Understanding FFI type/spec

Previous Topic Next Topic
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view

Understanding FFI type/spec

Nicolas Cellier
I'm still struggling with FFI.
And more specifically ExternalType and compiledSpec.

The headerWord of the compiledSpec is composed of those bits:
    atomicTypeCode - atomic-pointer-struct bits - byteSize
     (4 bits used/8 high bits) - (3 bits used/8 bits) - (16 low bits)

So, the atomic type short is encoded with atomicType=5, atomicBit(4),size=2:
    ExternalType short compiledSpec first hex -> '16r5040002'

and short *, is same with pointerBit(2) set, and size=pointer size (4 on 32bits)
    ExternalType short asPointerType compiledSpec first hex -> '16r5060004'

Note that I had to correct the pointer size for 64 bits in FFI-Kernel-nice.52!!!
(I corrected the same bug in the plugin a few days back, so it's better to match).

Now what happens with an ExternalStructure? it has the structureBit(1) set:
    (ExternalType structTypeNamed: #FFITestPoint2) compiledSpec first hex -> '16r10008'

OK, and what about FFITestPoint2 *?
    (ExternalType structTypeNamed: #FFITestPoint2) asPointerType compiledSpec first hex -> '16r20004'

Ah, surprising, it has the pointerBit(2) set, but no structureBit nor atomicBit.
My own expectation would be 16r30004...
So I thought that this could have been on purpose to match void *, but no:
    ExternalType void asPointerType compiledSpec first hex -> '16r60004'

And what about type aliases?
    (ExternalType structTypeNamed: #Win32Handle) compiledSpec first hex -> '16r6040004'

Since it is an alias for ulong (understand uint32_t), we get the same compiledSpec
    ExternalType ulong compiledSpec first hex -> '16r6040004'

OK, and what about a Win32Handle* (if it ever makes sense)
    (ExternalType structTypeNamed: #Win32Handle) asPointerType compiledSpec first hex -> '16r20004'
Ouch ! Doubly surprising, it's like any other structure!
If I want to alias a type, is the pointer to the alias really going to work as expected???
    ExternalType ulong asPointerType compiledSpec first hex -> '16r6060004'

And now, let's insepct some code:
    "Return true if the receiver represents a structure type"
    ^self headerWord anyMask: FFIFlagStructure

    "Return true if the receiver represents a pointer type"
    ^self isStructureType not and:[self headerWord anyMask: FFIFlagPointer]

embeddedSpecWithSize: typeSize
    "Return a compiled spec for embedding in a new compiled spec."
    | spec header |
    spec := self compiledSpec copy.
    header := spec at: 1.
    header := (header bitAnd: FFIStructSizeMask bitInvert32) bitOr: typeSize.
    spec at: 1 put: header.
    (self isStructureType and:[self isPointerType not])
        ifTrue:[spec := spec copyWith: self class structureSpec].

I absolutely do not understand such code.
1- (self isStructureType and:[self isPointerType not]) is always false with the above definitions!!!
2- If it were not, I don't understand the intention to follow embedded structures (nested structures) specs with a void structure spec 16r10000, nor where it is used...

And yet another obscure and questionable choice: alias to a pointer:

compileAlias: spec withAccessors: aBool
        ifTrue:[compiledSpec := WordArray with:
                    (ExternalType structureSpec bitOr: ExternalType pointerSpec)]
        ifFalse:[compiledSpec := externalType compiledSpec].

So finally, the combination pointerBit(2)+structureBit(1) is used in the system, not exactly where I expected, but when a type is an alias to a pointer (but remember, not necessarily a pointer to a structure).
    (ExternalType structTypeNamed: #MacPixPatPtr) compiledSpec first hex -> '16r30004'

Completely brainfuck: the least I expect from such niceties is looonng and well detailed explanation. Where is the F... manual?

With this ability of human brain to perceive order in perfectly random patterns, I'm just beginning to see the light: for some reasons, we want an alias to a pointer not being handled as a pointer, thus the special use case for pointerBit+structureBit (16r30004) and thus the bizarre definition of isPointerType... Now what are those reasons?

I'm in a situation where I have plenty of very logical FFI declarations that just don't work.
I thought I had understood enough of FFI to propose some corrections to the plugin.
But now, I'm not even sure. And maintaining backward compatibility with obscure undocumented features is going to be very challenging...

Sometimes, the Pharo style scorched earth policy is very tempting ;)
Or I can just fork, implement the missing multiple-pointer-level spec, the missing array spec, the missing union spec, correct the 64bits slips, handle the alignment...
Before inventing my own square wheel, I will have another look at Esteban's UFFI, he has been there before me.