-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable user code to determine the alignment needs of types #29235
Comments
Wouldn't the runtime need to expose it? dotnet/coreclr |
@johnkellyoxford - IL provides a very useful opcode that I may have used in the past for something like this, I should look at that code (it's not immediately to hand by the way) but I believe I used this to determine the in-memory (that is CLR memory) size of structs before (Why did I think there was an IL opcode for getting a field's offset...). By creating a small dynamic IL delegate specific for some type, we can determine the alignment I think. We'd (in explicit IL) create instance of I think this is one way we - ordinary developers - could implement this, but having it added to the The only "flaw" here (but a pretty harmless one) is that we may find some type gets aligned on a 16 byte boundary (using the dynamic IL idea above) purely by fluke, when in fact it's actual alignment needs might only be 4 bytes, but this is fine really. |
that is an issue, because if for example, you get the pointer value 8192, you might think it is 8192 byte aligned, which might cause some issues.... |
@johnkellyoxford - Hmm, yep nice one, you are correct; I think this needs additional thinking... |
Also, getting a wrong value seems a bit of an error. Easier to call into the runtime to ask for it |
@johnkellyoxford - Looking at typical generated IL for this kind of thing reveals little. For example, this C#: using System;
public class C {
public void M() {
var x = "";
var d = new Data();
d.a = 8;
}
}
public struct Data
{
public byte a;
double b;
}
generates (among other things):
The So it's not clear how the memory for the item Data (local 1) is actually allocated... |
I think you are misunderstanding. *Except in cases of explicit |
@johnkellyoxford - Yes, I do have only a partial understanding of this, much of it being inferred. It seems then that the code that actually does the allocation of the struct (in the above example) is not written in IL but external to it, and simply presumed when we look at the IL. So I guess there are primitives that are specific to each platform, and these primitives are the things that are "aware" of the alignment details...very interesting... |
Correct. To put it another way: IL runs on a theoretical virtual machine. The CLR implementation translates the IL representation into one which the target machine (x86, ARM, whatever) understands before it runs. I don't believe the VM ever cares about things like memory alignment, but the target machine might. So it's up to the CLR implementation to care about these things. (This is why I said in the other issue that the CLR developers might be hesitant to expose something like this. You might in theory have an object allocated with a different alignment depending on whether it was allocated on the stack or the heap. Or maybe special SIMD types trigger special alignment, but only when the runtime thinks SIMD instructions might be used on it. By exposing that implementation detail, they lose flexibility in this regard.) |
@PathogenDavid We have |
I'm not arguing against exposing the alignment, just warning @Korporal that it might be higher-friction than he thinks. I'd actually really like to see CoreCLR make more guarantees around alignment.
Why? Size should not affect alignment at all. I can allocate a 32 byte struct at 0x4000000 or 0x4000001. It'll have the same size, but only one of them will be aligned to a 2 byte boundary. (Also SizeOf is much more important to interop scenarios than alignment is, so I don't think the early .NET Devs could've gotten away without it even if they wanted to. For instance, Windows is full of structures that need to be initialized with their own size, a quick grep says there's 1,323 such instances of |
You can link to specific line ranges on GitHub, BTW: https://github.com/dotnet/coreclr/blob/72d49127a0c25e4b931c81e621c2411bfb6633a5/src/vm/class.h#L383-L391 |
Hmm, is it best to expose it as
|
WIP - Adds a method which exposes the internal EEClassLayoutInfo alignment members. Namely, returns the max of unmanaged alignment and managed alignment, to allow passing the type between the two types of code Related dotnet/corefx#36792
@johnkellyoxford @PathogenDavid Very interesting guys. Ultimately the goal is to ensure that we can create and return a So I don't see any conceptual difference between a "managed alignment" and a "native alignment" in this regard. Ultimately every datum refers to an actual memory address which is by definition a physical thing not a managed thing. I'd be curious to see a Anyway what are these "SIMD' types I hear about? types that do (might?) need alignment > 8? |
My assumption is that it is for structs which are marshaled, so it wouldn't be relevant in your case. I'm actually not entirely certain if either of these two fields are what you actually want. They're only used for debug output and field marshaling unless I missed something. They don't appear to directly influence allocation.
SIMD is short for "Single Instruction, Multiple Data". Multiple data: At a hardware level you have special large register that are divided up into multiple discrete values. Single instruction: There are special instructions (big list for x86 here) that operate on multiple values at once in regards to these special registers. For example, the In the context of x86, SIMD types are generally the only time when you need to start caring about alignment because some load/store operations working with those registers have alignment requirements. In .NET, SIMD types are exposed under the |
Honestly, if I were you, I'd just respect 8 or 16 byte alignment by default and just work with that. It's easiest |
@Korporal I am rapidly starting to think that instead of asking for a new API, you need to ask a JIT expert if you even need the API in the first place. I'm pretty confident that you don't. If you're writing a custom allocator on the native side for some reason, make sure everything is word-aligned. If the native side has stricter alignment requirements for SIMD, make sure its meeting those requirements. Otherwise I think you're worrying about this way too much. |
CRT's malloc aligns to 8 bytes on 32-bit and 16 on 64-bit. glibc's malloc is a little more vague with "suitably aligned for any built-in type". (Comment in the implementation says it's double the word size, which is the same as the CRT.) |
Seems you can now do this calculation yourself and it gets optimized pretty well by the JIT, as mentioned here:
I updated my interop helper for alignment calculation and made a few more tests, structs look very good, a single constant load, but alignment of primitives is not perfectly optimized yet, but still pretty good: https://godbolt.org/z/vTMxasnf7 public static class InteropHelper {
private struct AlignmentCheck<T> where T : unmanaged {
public byte Padding;
public T Content;
}
[SkipLocalsInit, MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int AlignmentOf<T>() where T : unmanaged {
Unsafe.SkipInit(out AlignmentCheck<T> container);
return (int)Unsafe.ByteOffset(ref container.Padding, ref Unsafe.As<T, byte>(ref container.Content));
}
} full example and optimization resultspublic static class C {
private struct AlignmentCheck<T> where T : unmanaged {
public byte Padding;
public T Content;
}
[SkipLocalsInit, MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int AlignmentOf<T>() where T : unmanaged {
Unsafe.SkipInit(out AlignmentCheck<T> container);
return (int)Unsafe.ByteOffset(ref container.Padding, ref Unsafe.As<T, byte>(ref container.Content));
}
public static int GetShortByteKVPAlignment() => AlignmentOf<KeyValuePair<short, byte>>();
public static int GetDoubleShortKVPAlignment() => AlignmentOf<KeyValuePair<double, short>>();
public static int GetShortByteVTAlignment() => AlignmentOf<(short, byte)>();
public static int GetDoubleShortVTAlignment() => AlignmentOf<(double, short)>();
public static int GetDecimalAlignment() => AlignmentOf<decimal>();
public static int GetDoubleAlignment() => AlignmentOf<double>();
public static int GetShortAlignment() => AlignmentOf<short>();
} optimizes to
|
I've used something like this previously: public static class Helpers
{
private struct AlignHelper<T>
{
T value;
byte b;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int AlignmentOf<T>() => (int)(sizeof(AlignHelper<T>) - sizeof(T));
} I don't see why this wouldn't work, and it probably provides better codegen, since there's nothing to pretend to initialise. |
Note that this computes the packing of There are types, such as |
This is to discuss adding a capability for managed code to determine the memory alignment needs of any unmanaged struct. For certain interop scenarios we may need to allocate struct instances outside the AppDomain (for example in user heap accessible to other code, perhaps native code).
These "manually" allocated instances would be exposed to managed code as ref values.
However when the raw memory is allocated we must ensure that the (artificially generated) ref is aligned in the same way it would be aligned by the CLR so that these returned ref values are wholly compatible to managed code.
A struct containing just byte fields for example can be aligned more flexibly than a struct containing just double fields or decimal fields, in principle we could craft code to analyze the struct since we know the alignment needs of all primitives and the padding rules used by the CLR, but to future proof this more is needed, for example SIMD it seems can have stringent alignment needs..
The text was updated successfully, but these errors were encountered: