Replies: 3 comments 18 replies
The code is notably not safe and wasn't before either. It is a "union" (and one where the "tag" is passed externally) and that comes with special aliasing considerations and other edge cases. Particularly for arbitrary types, types with padding exposed, types where not all bit representations are considered legal, and other scenarios it is explicitly dangerous and violates memory safety.
This is not safe. A simple example is Further, we may have reference assemblies which don't explicitly track all implementation fields; but a type is also free to version over time and add additional fields as well. Lots of special considerations exist and while many may not apply to your specific scenario, there is nothing that C# can do to prove the safety, its ultimately on the developer.
I'd note that this is not a correct definition either. <type category="union" name="VkClearColorValue" comment="// Union allowing specification of floating-point, integer, or unsigned integer color data. Actual value selected is based on image/attachment being cleared.">
<member><type>float</type> <name>float32</name>[4]</member>
<member><type>int32_t</type> <name>int32</name>[4]</member>
<member><type>uint32_t</type> <name>uint32</name>[4]</member>
</type>Which officially corresponds to the following in C: typedef union VkClearColorValue {
float float32[4];
int32_t int32[4];
uint32_t uint32[4];
} VkClearColorValue;And so the valid minimal C# implementation is something like: [StructLayout(LayoutKind.Explicit)]
public partial struct VkClearColorValue
{
[FieldOffset(0)]
public InlineArray4<float> float32;
[FieldOffset(0)]
public InlineArray4<int> int32;
[FieldOffset(0)]
public InlineArray4<uint> uint32;
}Strictly speaking, structs, unions, inline arrays, bitfields, signedness of types, and many other components have special ABI rules and that differs from manually inlining fields, specifying field offsets, or other considerations. This means that deviating from the 1-to-1 C definition can break things and lead to subtle incompatibilities on various platforms or configurations. These "happen" to line up and be equivalent in many cases, but it isn't a guarantee and you're often pessimizing codegen, interop, optimizations and other considerations by deviating in the ways you do. https://github.com/terrafx/terrafx.interop.vulkan is an example of correct bindings that are 1-to-1 generated using ClangSharp and so should be strictly ABI compatible on all current platforms that .NET supports. |
|
You and your AI have missed my point. Trying to persuade me that my code is invalid is not what this discussion is about (btw, my code is 100% valid and correct and does not require .Net 10 as your version with InlineArray4). Also, as I have written, there are cases where using explicit layout is unsafe. I have mentioned the referenced type. I agree that using decimal is also not safe. And there are probably some other types that are unsafe too. Access to 3 padding bytes (in your example with S1 and S2) is in my opinion not unsafe, but I agree that this is an edge case. But both of my samples are 100% safe. There is no way that writing or reading from those two structs would violate memory safety. If you find a case where this is not true, please provide an exact explanation. If you cannot do that, then you should agree that both of my examples are safe. And if you can say that, then the compiler can determine that too. Each year I read the whole blog post about new optimization techniques in new versions of .Net. It says the compiler can do very complex analyses of the code to provide optimized assembly code. Therefore, I cannot agree with your statement that "there is nothing that C# can do to prove the safety". I am sure that the compiler can analyze the struct with an explicit layout and determine if it is safe. For example:
If the compiler finds that the struct is not safe, then the I am sure that your goal is for as many developers as possible to switch to the latest version of .Net. The great new features that come with each version are very good motivation to do that. But having breaking changes is not. Increasing the .NET version in csproj and finding hundreds of errors after compiling is not a good experience. I think that if the compiler can analyze the structs and report errors only for truly unsafe structs, this would be much better. Forcing developers with interop code to add If those changes stay as they are, then this is not a problem for me. But in your last blog post about .Net 11 Preview 7, you asked for a review, and here it is from my side. And I still stand behind it. |
|
nit:
That's not true, the |
Uh oh!
There was an error while loading. Please reload this page.
I am the author of Ab4d.SharpEngine, which uses Vulkan to render 3D graphics. Because of that, I must use a lot of unsafe code.
I have tested the engine with the .Net 11 preview 7 because of the new Memory-safety model.
From my perspective, I find most of the changes in the new model great - especially that stackalloc and the address of a local variable are no longer considered unsafe. Also, preventing overriding a safe method with an unsafe method is great.
But I have trouble with the new
safekeyword that needs to be used for structures with explicit layout. Without that keyword, the following compiler error is produced:error CS9392: Field in an explicit or extended layout type must be marked 'unsafe' or 'safe'.Here are two examples:
This code is generated from the vk.xml file that describes the Vulkan API.
This is part of the struct that must exactly match the layout of what the shader expects (generated based on shader compiler output).
My problems:
Both structs contain only simple value types so there is no way a user would be able to write to the memory outside the struct or break an address of a referenced type.
What is the added value of adding the
safekeyword to existing code? The code that was already considered to be safe now requires an explicitsafekeyword. But isn't theunsafekeyword already enough to mark which code is safe and which is unsafe. This is true for all the code, except inside structs with explicit layout.The biggest problem for me is that my source code needs to compile for multiple versions of .Net. Because the
safeneeds to be added after thepublickeyword, the only way to be able to compile this for .Net 10 (and older) and .Net 11 is to use#if NET11_0_OR_GREATER. There I have two options:A) To duplicate the whole struct. This is risky because very delicate field offset values may get out of sync, and this is then very difficult to spot.
B) Use #if for each field. This produces a very ugly mess of code, for example:
My recommendations:
A. The compiler should analyze the struct, and in case there are no referenced types defined, then the struct is considered safe. Note that in my case only checking if the struct is blittable is not enough, because the second struct also defines Color3 and Color4 that are simple blittable structs but this makes the StandardMaterialUniformBuffer non-blittable but still safe.
B. Instead of marking each field, there should be an option to mark the whole struct as safe or unsafe. In my opinion, using only
unsafewould be enough.Additional notes:
The
safekeyword must now also be used for pInvoke withexterncode.I use that rarely so this is not a problem for me. But there is a lot of older code "around" that uses pInvoke and this code will suddenly not compile anymore. The developers will probably need to add the ugly
#if NET11_0_OR_GREATERand this will surely not make them happy. So maybe you can also consider removing thesafekeyword there.Those are my recommendations.
But otherwise, congratulations on the great work you are doing with each new version of .Net!
All reactions