You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[FFI] Allow to pass CData into struct and/or union fields. #11934
<?php$ffi = FFI::new('struct { uint64_t field; }');
$value = FFI::new('struct __attribute__((__packed__)) { uint32_t lo; uint32_t hi; }');
$value->lo = 0x42; // First 32 bytes$value->hi = 0xDEAD_BEEF; // Second 32 bytes// Because PHP (x86) does not support uint64.// On 64-bit PHP, this can be done with binary conversions (int64 -> uint64), // but this is not so convenient.$ffi->field = FFI::cast('uint64_t', FFI::addr($value));
Resulted in this output:
PHP Warning: Object of class FFI\CData could not be converted to int
I think it makes sense to do automatic boxing/unboxing to int, at least in the same way as it is done with GMP. However, I still have no idea how to implement this without breaking backwards compatibility =\\
i.e. using the same strategy as the default case, and using a fallible zval_try_get_long call. Ofc, this needs to be applied to the other cases too, and that code would have to be deduplicated.
I was thinking about using zend_ffi_is_compatible_type and if types are compatible copying memory (with maybe some direct assignments for short types). I think changing every case is just wasteful.
Depends on the interpretation of "changing every case" ig :P
Only annoying thing with the copies instead of direct assignments is endianness (for things like *(uint32_t*)ptr = lval etc).
Shouldn't endianness be the same for types with the same sizes? I'm assuming you cannot assign CData(char) to uint32_t, and if you try to assign to/from struct or array, I'm expecting same byte order anyway (not sure if this is allowed in that function). I think FFI::Cast does something similar, but instead pointer it'd be a memcpy.
Shouldn't endianness be the same for types with the same sizes?
It is, but what I mean by that is when the cases are generalised into a single one, then we'll have to convert *(whatever_type*)ptr = lval to a memcpy for convenience for the IS_LONG zval cases. This is where the endianness difference plays a role.
From the point of view of BC: Maybe it makes sense to introduce the FFI::get() and FFI::set() methods for explicitly setting and getting a value in the form of a CData?
I'm not sure what the best way to approach the BC is. I guess the BC break isn't that bad because we're replacing a warning of something that didn't work with something that works?
But I'm not a user of FFI myself, so I can't properly judge the BC.
I'm not sure what you mean, generalized would be only for CData, and this could be even limited to the same size CData.
I feel like we actually want to approach this the same way, but we're having some miscommunication.
I think I get now what you mean by I think changing every case is just wasteful. I interpreted this as you wanting to have fallthrough cases to handle the assignment, but I now think you meant checking for this case at the very start of the function.
In any case, feel free to work on this if you want, I don't want to steal this from you :)
In any case, feel free to work on this if you want, I don't want to steal this from you :)
I don't mind :D I've assumed nobody really wants to touch FFI. I'm really impressed how many things you're doing here!
:D I've filed #11935 that implements this. It's too late for 8.3 because we're past the feature freeze, but better late than never.
Hopefully I didn't overlook anything.
I think it makes sense to ask release managers (like Pierrick Charron. Alas, I do not know his nickname on the github to ping) if this is it possible to add this feature/fix.
Activity
KapitanOczywisty commentedon Aug 10, 2023
I think code responsible for that is here: ffi.c#L734. Though, I'm not sure how viable is adding CData.
However you can do some trickery to have similar effect:
Also note that calling some methods statically will be deprecated in PHP 8.3: https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#fficast_ffinew_and_ffitype
SerafimArts commentedon Aug 10, 2023
Yes, I know, but I used a more readable and convenient option as an example.
The point here is not that it is impossible to fix this problem. But rather, that you have to fence a lot of non-obvious code (overengineer) for this.
P.S. there is another hack through set by pointer, but I could not check the correctness of this option:
KapitanOczywisty commentedon Aug 10, 2023
Assigning CData would be basically
memcpy, just withoutsizeargument and I'm not entirely sure this can be done cleanly, but I'll take a look.ndossche commentedon Aug 10, 2023
Ah, I was also looking at it and only now see your message. A simple PoC would be:
i.e. using the same strategy as the default case, and using a fallible
zval_try_get_longcall. Ofc, this needs to be applied to the other cases too, and that code would have to be deduplicated.KapitanOczywisty commentedon Aug 10, 2023
I was thinking about using
zend_ffi_is_compatible_typeand if types are compatible copying memory (with maybe some direct assignments for short types). I think changing everycaseis just wasteful.ndossche commentedon Aug 10, 2023
Depends on the interpretation of "changing every case" ig :P
Only annoying thing with the copies instead of direct assignments is endianness (for things like
*(uint32_t*)ptr = lvaletc).KapitanOczywisty commentedon Aug 10, 2023
Shouldn't endianness be the same for types with the same sizes? I'm assuming you cannot assign
CData(char)touint32_t, and if you try to assign to/from struct or array, I'm expecting same byte order anyway (not sure if this is allowed in that function). I thinkFFI::Castdoes something similar, but instead pointer it'd be amemcpy.ndossche commentedon Aug 10, 2023
It is, but what I mean by that is when the cases are generalised into a single one, then we'll have to convert
*(whatever_type*)ptr = lvalto amemcpyfor convenience for the IS_LONG zval cases. This is where the endianness difference plays a role.SerafimArts commentedon Aug 10, 2023
From the point of view of BC: Maybe it makes sense to introduce the
FFI::get()andFFI::set()methods for explicitly setting and getting a value in the form of a CData?P.S. And it can do the same with functions.
...although I don't remember exactly in which cases the return results of functions are converted to PHP types, instead of CData
ndossche commentedon Aug 10, 2023
I'm not sure what the best way to approach the BC is. I guess the BC break isn't that bad because we're replacing a warning of something that didn't work with something that works?
But I'm not a user of FFI myself, so I can't properly judge the BC.
KapitanOczywisty commentedon Aug 10, 2023
I'm not sure what you mean, generalized would be only for CData, and this could be even limited to the same size CData.
CData wasn't supposed to be supported at all for fields, so "none". Existing assignments would remain the same.
ndossche commentedon Aug 10, 2023
I feel like we actually want to approach this the same way, but we're having some miscommunication.
I think I get now what you mean by
I think changing every case is just wasteful. I interpreted this as you wanting to have fallthrough cases to handle the assignment, but I now think you meant checking for this case at the very start of the function.In any case, feel free to work on this if you want, I don't want to steal this from you :)
KapitanOczywisty commentedon Aug 10, 2023
Yes, exactly, sorry about that.
I don't mind :D I've assumed nobody really wants to touch FFI. I'm really impressed how many things you're doing here!
Implement phpGH-11934: Allow to pass CData into struct and/or union f…
ndossche commentedon Aug 10, 2023
No worries, glad we sorted this out.
:D I've filed #11935 that implements this. It's too late for 8.3 because we're past the feature freeze, but better late than never.
Hopefully I didn't overlook anything.
SerafimArts commentedon Aug 10, 2023
Hmm, I think FFI is not popular enough to take this into account: https://packagist.org/?query=ffi&tags=ffi Almost all popular packages are written by me.
I think it makes sense to ask release managers (like Pierrick Charron. Alas, I do not know his nickname on the github to ping) if this is it possible to add this feature/fix.
Girgias commentedon Aug 10, 2023
The RMs are @adoy, @bukka, and @ericmann if needed.
Implement GH-11934: Allow to pass CData into struct and/or union fields
ndossche commentedon Aug 29, 2023
Merged for 8.3 just in time.