Workaround unavoidable marshaling on function pointers #99
Labels
Area-OutputGeneration
Issues concerning the process of generating output from Biohazrd
Bug
Concept-Correctness
Issues concerning the correctness of the translation from an ABI perspective
Concept-OutputUsability
Issues concerning whether or not the output from Biohazrd is actually usable
Language-C#
Issues specific to C#
Workaround
Due to its heritage as a Windows technology, the CLR really wants to pretend
sizeof(bool) == 4
for the purposes of marshaling. Typically you can avoid this by usingMarshalAs(I1)
. Thebool
will still be marshaled to eliminate invalid boolean values (non-0/1 bools), but it'll be 1 byte at least. Unfortunately, it turns out thecalli
instruction (which is what function pointer calls are under the hood) perform marshaling too. (For some reason I was under the impression they didn't until I had a a "fun" time debugging bad marshaling in a case where a function returned a bool viaal
when the CLR usedeax
. This does make sense though sincecalli
was used heavily for C++/CLI and is expected: dotnet/runtime#40908)In the case of char, the situation is normally better because if you mark it as being marshaled as
U2
,U1
, or default marshaling on a Unicode PInvoke/struct it will become blittable. (Source) You can enforce this behavior across the entire module usingDefaultCharSetAttribute
. Unfortunately, again we can't useMarshalAs
or similar on function pointers andDefaultCharSetAttribute
does not apply to function pointers. (Whether this should be considered a bug is somewhat debatable since it'd make two modules with different default charsets incompatible for function pointer passing.)In the long term, we'd like to see an escape hatch added to the CLR to allow these types to be safely used in unmanaged function pointers, and I'm working on a duo (now triplet) of proposals to improve the situation. Unfortunately we need a fix today.
As a (hopefully temporary) workaround, Biohazrd will synthesize two tiny wrapper structs to avoid marshaling.
One for booleans: (SharpLab with JIT comparisons)
And another for chars: (SharpLab with JIT comparisons)
One thing to note from the JIT disassembly is that for whatever reason the JIT does not enregister these structs for some reason. (It does enregister similar structs that wrap
int
, so I assume this is an issue of their size.) This probably causes a small perf hit with these structs, but it will pass them on the stack and in registers for P/Invokes. It seems like dotnet/runtime#37745 (which fixed dotnet/runtime#8016) should've caused these to be enregistered, but I need to look into it more.It is unclear how valuable implementing the various instance methods on
System.Boolean
andSystem.Char
on these types will be. In general native consumers should not use these types directly. but they can be desirable for making a native function calls where the return value is immediately consumed feel more natural. Initially I won't provide these, but they will likely be added in later.Ideally we should not expose these types anywhere on the generated API surface except where absolutely necessary. Eventually we should support a few generation modes:
bool
orchar
)bool
orchar
ever.The text was updated successfully, but these errors were encountered: