-
Notifications
You must be signed in to change notification settings - Fork 34
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
[Lang] Revisit memory model #321
base: main
Are you sure you want to change the base?
Conversation
This change cleans up some of the wording in the memory model and clearly defines the terms _byte_, _memory location_, _memory access_, and _memory operation_. These terms will be useful in writing the Classes chapter where the layout of objects needs to be defined.
Co-authored-by: Damyan Pepper <[email protected]>
Co-authored-by: Damyan Pepper <[email protected]>
Co-authored-by: Damyan Pepper <[email protected]>
specs/language/introduction.tex
Outdated
must not alter memory at a location not contained in the set of memory locations it | ||
is operating on\footnote{Two subtle notes here: (1) A bit-field's memory location | ||
includes adjacent bit-fields, so reads and writes to bit-fields are expected to | ||
read and write adjacent memory if they're within the same set of locations, (2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me the usage of 'same set of locations' is a bit ambiguous. After a few reads I assume it means the adjacent bit-fields. Maybe something like:
read and write adjacent memory if they're within the same set of locations, (2) | |
read and write adjacent memory if they're within the adjacent bitfields' memory locations, (2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me the usage of 'same set of locations' is a bit ambiguous. After a few reads I assume it means the adjacent bit-fields. Maybe something like:
Maybe a caveat is needed to specify the adjacent bitfields' memory locations, do not ALSO include their adjacent bitfields.
specs/language/introduction.tex
Outdated
and a 128-bit \textit{minimum alignment}. | ||
\end{note} | ||
|
||
\p A memory location in any space may overlap with another memory location in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this mean you have say a MAU of 4 bytes, but you can have a memory location that accesses at byte 0 and a different memory location that accesses at byte 2?
specs/language/introduction.tex
Outdated
\p A memory location in any space may overlap with another memory location in | ||
the same space. A memory location in thread or threadgroup memory may not | ||
overlap with memory locations in any other memory spaces. It is implementation | ||
defined if memory locations in other memory spaces alias with memory locations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sentence here makes me wonder if I'm interpreting the first sentence in this paragraph correctly.
Edit: it didn't tag the line correctly. I mean line 314.
specs/language/introduction.tex
Outdated
|
||
\SubSub{Constant Memory}{Intro.Memory.Spaces.Overlap} | ||
|
||
\p The \textbf{Thread} and \textbf{Thread Group} memory spaces may not overlap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this paragraph just stating part of what was said in the paragraph starting on line 311?
Nit: I was initially confused by the use of both 'memory location' and 'address'; I had just glazed over the fact that they are defined to be the same thing. It might be nice to use one or the other.
specs/language/introduction.tex
Outdated
Operations that perform memory accesses are called \textit{memory operations}. A | ||
memory operation may operate on one or more memory locations. A memory operation | ||
must not alter memory at a location not contained in the set of memory locations it | ||
is operating on\footnote{Two subtle notes here: (1) A bit-field's memory location |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defining the commonly understood term memory access granularity
and specifying the access granularity to be a byte
along with subsequent usage of the term may be a better option than using "bit-fields".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bit-fields are a specific language structure that has unique properties in the memory model because of their unique packing behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The section on bit-fields is much clearer now.
abstractions over physical memory. Each memory space has a defined \textit{line | ||
width}, which specifies the minimum readable and writable size, and a | ||
\textit{minimum alignment}, which defines the smallest addressable increment of | ||
the memory space. The two values need not be the same, although they may be. | ||
|
||
\begin{note} | ||
\p Memory accesses for many resource types in \gls{dx} operate on 128-bit | ||
slots aligned on 128-bit boundaries. In the terms of this specification it | ||
would be said that those memory spaces have a 128-bit \textit{line width}, | ||
and a 128-bit \textit{minimum alignment}. | ||
\end{note} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few constraints around memory accesses in HLSL and DXIL that you're trying to abstract over here, but I'm not sure the "line width" idea captures them effectively. In some sense it might seem nice to boil down some similar rules into a simple concept, but it's worth noting why the rules are what they are and how they might change.
- "Legacy" cbuffer and tbuffer layout. That is, the only cbuffer layout. Here, we have a constraint that came from 16-byte DXBC registers. The cbufferLoadLegacy docs call this a "row" in a comment, but I don't know that there's ever been any official terminology. Here, the rules on how big a single object or element of an array can be (128 bits) come from the packing rules, and it would arguably better to just write a section on those rules akin to the notes in maraneshi's layout visualizer rather than try to discuss this as a general rule about access size.
-
Data access via TypedBuffer. This is presumably where the "line width" idea comes from, but a lot of its complexity is unnecessary if we disallow "types that happen to fit" as type arguments to
Buffer<>
. Here, we have accesses to typed buffers and textures, and the operation that accesses them operates on a 4-element contained type. ABuffer<float>
is really aBuffer<float4>
that we only use one element from.This gets a bit confusing for 64 bit types. Notably,
Buffer<double4>
is not valid HLSL. However, this is really an implementation detail leaking through since the storage actually splits doubles up into int32 parts. So it's probably better to just think ofBuffer<double2>
as syntactic sugar for the casts and just call this kind of memory access what it is - access into a container of 4 at-most 32-bit values. -
Vectors of more than 4 elements don't exist in HLSL. This is simply due to the fact that there's a fixed set of vector types and no way for a user to create their own. It isn't a meaningful rule, and in spaces like local device memory we really don't need any constraints on the language here. If it's possible to write a
double8
somehow in the future in the language and that isn't used in constant or typed buffers specifically, it's straightforward for implementations to do whatever they need to do to lower it. I don't think we want to define an artificial limit there.
So I guess TLDR I think we should simply say two separate things rather than trying to define "line width":
- Objects in Constant Buffer Memory are laid out according to the constant buffer packing rules (to be defined later). Elements of structures and arrays in this layout cannot exceed 128 bits in size.
- Memory accesses into typed buffers are defined to access 4 elements of at-most 32-bit values. (Also possibly a note about emulation 64 bit values, though this may not belong in this section)
Also note that I use "constant buffer" memory in my wording above, rather than "constant memory". We may want to keep that terminology available for if we ever do something in that space that doesn't carry the constant buffer legacy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
I think part of the problem is trying to view all resource accesses as if they are like native memory accesses from the shader, with only the address space placing constraints on alignment and such. I think we can evolve constant memory and raw/structured buffer memory in this direction, but not typed/texture accesses. For memory that goes in this direction, I don't think "line width" would be a concept we want to use/keep, and "minimum alignment" will be defined in other ways, rather than by some fixed value applied to a memory type.
Some notes:
Elements of structures and arrays in this layout cannot exceed 128 bits in size.
I don't think I would agree with that. First, it's a confusing use of the term "element" here. Perhaps you had a different definition of "element" in mind than what I am interpreting here, but I struggle to think of a single definition that fits into this statement. Plenty of elements of structures and arrays in HLSL that exceed 128-bits in size can be placed into the constant buffer. You can declare a double4 (or array of such) in a constant buffer, which will use two rows for the vector. It's just that structures, array elements, and any type that cannot fit within the remainder of a row will be started at the beginning of the next available row. For some of these, that's part of the high-level packing rules, not necessarily something intrinsic to the DXIL interface. For array elements, they must be 128-bit aligned to ensure that array indexing maps to an index in the DXIL legacy constant buffer load op without impacting the index of the component read from the result.
For legacy constant buffer load in DXIL, it's important to note that this load op doesn't mean all of the components are loaded - only the components that are extracted from the result structure need to be loaded. It's a subtle difference, but important in certain circumstances, and mismatches the concept of "line width" as applied to constant buffers. Think of the DXIL op as a compromise as there wasn't an easy way to express the thing that's expressed easily in DXBC asm like so: CB0[0][0].yyyz
(only loads y
and z
components).
This change cleans up some of the wording in the memory model and clearly defines the terms byte, memory location, memory access, and memory operation.
These terms will be useful in writing the Classes chapter where the layout of objects needs to be defined.