From 59afdf01563d1bb50f5ffce436dc71473de257ae Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 16:06:29 -0700 Subject: [PATCH 01/12] Create InlineArrayAttribute.md --- docs/design/features/InlineArrayAttribute.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/design/features/InlineArrayAttribute.md diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md new file mode 100644 index 0000000000000..9c558e357c416 --- /dev/null +++ b/docs/design/features/InlineArrayAttribute.md @@ -0,0 +1 @@ +. From 4f9b506de3ff970eb2fdc274d1174b9c615bae65 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 16:12:17 -0700 Subject: [PATCH 02/12] Update InlineArrayAttribute.md --- docs/design/features/InlineArrayAttribute.md | 70 +++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 9c558e357c416..f37ab60531f94 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -1 +1,69 @@ -. +### Background and motivation + +There were multiple proposals and ideas in the past asking for a no-indirection primitive for inline data. [Example1(inline strings)](https://github.com/dotnet/csharplang/issues/2099), [Example2(inline arrays)](https://github.com/dotnet/runtime/issues/12320), [Example3(generalized fixed-buffers)](https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#safe-fixed-size-buffers) +Our existing offering in this area – [unsafe fixed-sized buffers](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#fixed-size-buffers) has multiple constraints, in particular it works only with blittable value types and provides no overrun/type safety, which considerably limits its use. + +The InlineArrayAttribute is a building block to allow efficient, type-safe, overrun-safe indexable/sliceable inline data. + +### API + +```C# +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)] + public sealed class InlineArrayAttribute : Attribute + { + public InlineArrayAttribute (int length) + { + Length = length; + } + + public int Length { get; } + } +} +``` + +When `InlineArray` attribute is applied to a struct with one instance field, it is interpreted by the runtime as a directive to replicate the layout of the struct `Length` times. That includes replicating GC tracking information if the struct happens to contain managed pointers. + +Unlike "filed0; field1; field2;..." approach, the resulting layout would be guaranteed to have the same order and packing details as elements of an array with element `[0]` matching the location of the single specified field. +That will allow the whole aggregate to be safely indexable/sliceable. + +`Length` must be greater than 0. + +struct must not have explicit layout. + +In cases when the attribute cannot have effect, it is an error case handled in the same way as the given platform handles cases when a type layout cannot be constructed. +Generally, it would be a `TypeLoadException` thrown at the time of layout construction. + +### API Usage + +```C# +// runtime replicates the layout of the struct 42 times +[InlineArray(Length = 42)] +struct MyArray +{ + private T _element0; + public Span SliceExample() + { + return MemoryMarshal.CreateSpan(ref _element0, 42); + } +} +``` +### Memory layout of an inline array instance. + +TBD + +### Special note on scenario when the element is readonly. + +TBD + +### FAQ: + +**Why do we put the attribute on the struct and not on the field?** + +Allowing the attribute on individual fields introduces numerous additional scenarios and combinations that make the feature considerably more complex. +- we would need to rationalize or somehow forbid the attribute usage on static, threadstatic, RVA fields +- allowing replicated storage in classes would need to account for base classes that also may have instance fields, and perhaps with replicated storage as well. +- allowing multiple replicated fields in the same struct could make the overall layout computation a fairly complex routine when field ordering, packing, ref-ness and alignment of the fields are considered. (i.e alignment would need to consider pre-replicated sizes, but packing post-replicated) + +All the above issues can be solved, but at a cost to the implementation complexity while most additional scenarios appear to be less common and easily solvable by providing a wrapper struct. From ee74ec1ce61e172046809d1e0682ab193fc12993 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 16:16:39 -0700 Subject: [PATCH 03/12] about readonly field --- docs/design/features/InlineArrayAttribute.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index f37ab60531f94..448f9f73919c8 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -55,7 +55,8 @@ TBD ### Special note on scenario when the element is readonly. -TBD +There is a scenario where the element field in a struct decorated with `InlineArrayAttribute` is `readonly`. +The `readonly` part in such scenario has no special semantics and as such the scenario is unsupported and is not recommended. ### FAQ: From 4a46fc35472482d96ae1bd1ad7bd98f00296a0df Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 16:53:23 -0700 Subject: [PATCH 04/12] On type layout --- docs/design/features/InlineArrayAttribute.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 448f9f73919c8..7b1f9eb21f1a0 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -51,7 +51,18 @@ struct MyArray ``` ### Memory layout of an inline array instance. -TBD +The memory layout of a struct instance decorated with `InlineArray` attribute closely matches the layout of the element sequence of an array `T[]` with length == `Length`. +In particular (using the `MyArray` example defined above): +* In unboxed form there is no object header or any other data before the first element. +Example: assuming the instance is not GC-movable, the following holds: `(byte*)*inst == (byte*)inst._element0` +* There is no additional padding between elements. +Example: assuming the instance is not GC-movable and `Length > 1`, the following will yield a pointer to the second element: `(byte*)inst._element0 + sizeof(T)` +* The size of the entire instance is the size of its element type multiplied by the `Length` +Example: the following holds: `sizeof(MyArray) == Length * sizeof(T)` +* Just like with any other struct, the boxed form will contain the regular object header followed by an entire unboxed instance. +Example: boxing/unboxing will result in exact copy on an entire instance: `object o = inst; MyArray inst1copy = (MyArray)o` + +Type T can be a reference type and can contain managed references. The runtime will ensure that objects reachable through elements of an inline array instance can be accessed in a type-safe manner. ### Special note on scenario when the element is readonly. From c87d02f658322b5e433f71316cea52d425482bbc Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 16:54:06 -0700 Subject: [PATCH 05/12] Update InlineArrayAttribute.md --- docs/design/features/InlineArrayAttribute.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 7b1f9eb21f1a0..5894088044529 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -53,13 +53,13 @@ struct MyArray The memory layout of a struct instance decorated with `InlineArray` attribute closely matches the layout of the element sequence of an array `T[]` with length == `Length`. In particular (using the `MyArray` example defined above): -* In unboxed form there is no object header or any other data before the first element. +* In unboxed form there is no object header or any other data before the first element. Example: assuming the instance is not GC-movable, the following holds: `(byte*)*inst == (byte*)inst._element0` -* There is no additional padding between elements. +* There is no additional padding between elements. Example: assuming the instance is not GC-movable and `Length > 1`, the following will yield a pointer to the second element: `(byte*)inst._element0 + sizeof(T)` -* The size of the entire instance is the size of its element type multiplied by the `Length` +* The size of the entire instance is the size of its element type multiplied by the `Length` Example: the following holds: `sizeof(MyArray) == Length * sizeof(T)` -* Just like with any other struct, the boxed form will contain the regular object header followed by an entire unboxed instance. +* Just like with any other struct, the boxed form will contain the regular object header followed by an entire unboxed instance. Example: boxing/unboxing will result in exact copy on an entire instance: `object o = inst; MyArray inst1copy = (MyArray)o` Type T can be a reference type and can contain managed references. The runtime will ensure that objects reachable through elements of an inline array instance can be accessed in a type-safe manner. From 91a2406cebd25ec24246b89b45e4937097c5984a Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 17:03:34 -0700 Subject: [PATCH 06/12] On max size --- docs/design/features/InlineArrayAttribute.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 5894088044529..a7d43a0cd3b76 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -63,6 +63,10 @@ Example: the following holds: `sizeof(MyArray) == Length * sizeof(T)` Example: boxing/unboxing will result in exact copy on an entire instance: `object o = inst; MyArray inst1copy = (MyArray)o` Type T can be a reference type and can contain managed references. The runtime will ensure that objects reachable through elements of an inline array instance can be accessed in a type-safe manner. + +### Size limits for inline array instances. +The size limits for inline array instances will match the size limits of structs on a given runtime implementation. +Generally this is a very large size imposed by the type system implementation and is rarely reachable in actual applications due to other limitations such as max stack size, max size of an object, and similar. ### Special note on scenario when the element is readonly. From 05cf73a1651bf8653c10238247b854425909bdcd Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 17:13:51 -0700 Subject: [PATCH 07/12] make linter happy --- docs/design/features/InlineArrayAttribute.md | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index a7d43a0cd3b76..11c93816b2631 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -1,7 +1,7 @@ ### Background and motivation There were multiple proposals and ideas in the past asking for a no-indirection primitive for inline data. [Example1(inline strings)](https://github.com/dotnet/csharplang/issues/2099), [Example2(inline arrays)](https://github.com/dotnet/runtime/issues/12320), [Example3(generalized fixed-buffers)](https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#safe-fixed-size-buffers) -Our existing offering in this area – [unsafe fixed-sized buffers](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#fixed-size-buffers) has multiple constraints, in particular it works only with blittable value types and provides no overrun/type safety, which considerably limits its use. +Our existing offering in this area – [unsafe fixed-sized buffers](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#fixed-size-buffers) has multiple constraints, in particular it works only with blittable value types and provides no overrun/type safety, which considerably limits its use. The InlineArrayAttribute is a building block to allow efficient, type-safe, overrun-safe indexable/sliceable inline data. @@ -21,8 +21,8 @@ namespace System.Runtime.CompilerServices public int Length { get; } } } -``` - +``` + When `InlineArray` attribute is applied to a struct with one instance field, it is interpreted by the runtime as a directive to replicate the layout of the struct `Length` times. That includes replicating GC tracking information if the struct happens to contain managed pointers. Unlike "filed0; field1; field2;..." approach, the resulting layout would be guaranteed to have the same order and packing details as elements of an array with element `[0]` matching the location of the single specified field. @@ -33,7 +33,7 @@ That will allow the whole aggregate to be safely indexable/sliceable. struct must not have explicit layout. In cases when the attribute cannot have effect, it is an error case handled in the same way as the given platform handles cases when a type layout cannot be constructed. -Generally, it would be a `TypeLoadException` thrown at the time of layout construction. +Generally, it would be a `TypeLoadException` thrown at the time of layout construction. ### API Usage @@ -53,19 +53,26 @@ struct MyArray The memory layout of a struct instance decorated with `InlineArray` attribute closely matches the layout of the element sequence of an array `T[]` with length == `Length`. In particular (using the `MyArray` example defined above): -* In unboxed form there is no object header or any other data before the first element. +* In unboxed form there is no object header or any other data before the first element. + Example: assuming the instance is not GC-movable, the following holds: `(byte*)*inst == (byte*)inst._element0` -* There is no additional padding between elements. + +* There is no additional padding between elements. + Example: assuming the instance is not GC-movable and `Length > 1`, the following will yield a pointer to the second element: `(byte*)inst._element0 + sizeof(T)` -* The size of the entire instance is the size of its element type multiplied by the `Length` + +* The size of the entire instance is the size of its element type multiplied by the `Length` + Example: the following holds: `sizeof(MyArray) == Length * sizeof(T)` -* Just like with any other struct, the boxed form will contain the regular object header followed by an entire unboxed instance. + +* Just like with any other struct, the boxed form will contain the regular object header followed by an entire unboxed instance. + Example: boxing/unboxing will result in exact copy on an entire instance: `object o = inst; MyArray inst1copy = (MyArray)o` Type T can be a reference type and can contain managed references. The runtime will ensure that objects reachable through elements of an inline array instance can be accessed in a type-safe manner. ### Size limits for inline array instances. -The size limits for inline array instances will match the size limits of structs on a given runtime implementation. +The size limits for inline array instances will match the size limits of structs on a given runtime implementation. Generally this is a very large size imposed by the type system implementation and is rarely reachable in actual applications due to other limitations such as max stack size, max size of an object, and similar. ### Special note on scenario when the element is readonly. From 4eb69fb3a714c98ea734a7a71ca7eaf1e398e961 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 17:18:47 -0700 Subject: [PATCH 08/12] more linter failures --- docs/design/features/InlineArrayAttribute.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 11c93816b2631..4d810b5e230f8 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -25,8 +25,7 @@ namespace System.Runtime.CompilerServices When `InlineArray` attribute is applied to a struct with one instance field, it is interpreted by the runtime as a directive to replicate the layout of the struct `Length` times. That includes replicating GC tracking information if the struct happens to contain managed pointers. -Unlike "filed0; field1; field2;..." approach, the resulting layout would be guaranteed to have the same order and packing details as elements of an array with element `[0]` matching the location of the single specified field. -That will allow the whole aggregate to be safely indexable/sliceable. +Unlike "filed0; field1; field2;..." approach, the resulting layout would be guaranteed to have the same order and packing details as elements of an array with element `[0]` matching the location of the single specified field. That will allow the whole aggregate to be safely indexable/sliceable. `Length` must be greater than 0. @@ -58,7 +57,7 @@ In particular (using the `MyArray` example defined above): Example: assuming the instance is not GC-movable, the following holds: `(byte*)*inst == (byte*)inst._element0` * There is no additional padding between elements. - + Example: assuming the instance is not GC-movable and `Length > 1`, the following will yield a pointer to the second element: `(byte*)inst._element0 + sizeof(T)` * The size of the entire instance is the size of its element type multiplied by the `Length` @@ -66,23 +65,24 @@ Example: assuming the instance is not GC-movable and `Length > 1`, the following Example: the following holds: `sizeof(MyArray) == Length * sizeof(T)` * Just like with any other struct, the boxed form will contain the regular object header followed by an entire unboxed instance. - + Example: boxing/unboxing will result in exact copy on an entire instance: `object o = inst; MyArray inst1copy = (MyArray)o` Type T can be a reference type and can contain managed references. The runtime will ensure that objects reachable through elements of an inline array instance can be accessed in a type-safe manner. ### Size limits for inline array instances. + The size limits for inline array instances will match the size limits of structs on a given runtime implementation. Generally this is a very large size imposed by the type system implementation and is rarely reachable in actual applications due to other limitations such as max stack size, max size of an object, and similar. - + ### Special note on scenario when the element is readonly. There is a scenario where the element field in a struct decorated with `InlineArrayAttribute` is `readonly`. The `readonly` part in such scenario has no special semantics and as such the scenario is unsupported and is not recommended. -### FAQ: +### FAQ: -**Why do we put the attribute on the struct and not on the field?** +**Why do we put the attribute on the struct and not on the field?** Allowing the attribute on individual fields introduces numerous additional scenarios and combinations that make the feature considerably more complex. - we would need to rationalize or somehow forbid the attribute usage on static, threadstatic, RVA fields From d11b7c4ab909007c49a6a72c1d6d7de52457830d Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 17:21:22 -0700 Subject: [PATCH 09/12] linter failure (hopefully the last one) --- docs/design/features/InlineArrayAttribute.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 4d810b5e230f8..4c063f91e6608 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -77,7 +77,8 @@ Generally this is a very large size imposed by the type system implementation an ### Special note on scenario when the element is readonly. -There is a scenario where the element field in a struct decorated with `InlineArrayAttribute` is `readonly`. +There is a scenario where the element field in a struct decorated with `InlineArrayAttribute` is `readonly`. + The `readonly` part in such scenario has no special semantics and as such the scenario is unsupported and is not recommended. ### FAQ: From 6e033b4668cb6ee68f72a662ca577764a05b5aaa Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Thu, 19 Sep 2024 17:30:40 -0700 Subject: [PATCH 10/12] small wording tweak --- docs/design/features/InlineArrayAttribute.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 4c063f91e6608..db59129893858 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -1,9 +1,9 @@ ### Background and motivation There were multiple proposals and ideas in the past asking for a no-indirection primitive for inline data. [Example1(inline strings)](https://github.com/dotnet/csharplang/issues/2099), [Example2(inline arrays)](https://github.com/dotnet/runtime/issues/12320), [Example3(generalized fixed-buffers)](https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#safe-fixed-size-buffers) -Our existing offering in this area – [unsafe fixed-sized buffers](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#fixed-size-buffers) has multiple constraints, in particular it works only with blittable value types and provides no overrun/type safety, which considerably limits its use. +Our preexisting offering in this area – [unsafe fixed-sized buffers](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#fixed-size-buffers) has multiple constraints, in particular it works only with blittable value types and provides no overrun/type safety, which considerably limits its use. -The InlineArrayAttribute is a building block to allow efficient, type-safe, overrun-safe indexable/sliceable inline data. +*The InlineArrayAttribute is a building block to allow efficient, type-safe, overrun-safe indexable/sliceable inline data.* ### API From 8f2411de3b063355c07676e31c512514f5219d93 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Fri, 20 Sep 2024 16:33:56 -0700 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: Jan Kotas --- docs/design/features/InlineArrayAttribute.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index db59129893858..6d7b905c9822c 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -1,6 +1,6 @@ ### Background and motivation -There were multiple proposals and ideas in the past asking for a no-indirection primitive for inline data. [Example1(inline strings)](https://github.com/dotnet/csharplang/issues/2099), [Example2(inline arrays)](https://github.com/dotnet/runtime/issues/12320), [Example3(generalized fixed-buffers)](https://github.com/dotnet/csharplang/blob/main/proposals/low-level-struct-improvements.md#safe-fixed-size-buffers) +There were multiple proposals and ideas in the past asking for a no-indirection primitive for inline data. [Example1(inline strings)](https://github.com/dotnet/csharplang/issues/2099), [Example2(inline arrays)](https://github.com/dotnet/runtime/issues/12320), [Example3(generalized fixed-buffers)](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-11.0/low-level-struct-improvements.md#safe-fixed-size-buffers) Our preexisting offering in this area – [unsafe fixed-sized buffers](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#fixed-size-buffers) has multiple constraints, in particular it works only with blittable value types and provides no overrun/type safety, which considerably limits its use. *The InlineArrayAttribute is a building block to allow efficient, type-safe, overrun-safe indexable/sliceable inline data.* From 7d18a38422a53f311eb3f2b0c4c492ba33ad34e1 Mon Sep 17 00:00:00 2001 From: Vladimir Sadov Date: Fri, 20 Sep 2024 16:45:10 -0700 Subject: [PATCH 12/12] Added a link to C# Inline Arrays, as an example where this is used. --- docs/design/features/InlineArrayAttribute.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/design/features/InlineArrayAttribute.md b/docs/design/features/InlineArrayAttribute.md index 6d7b905c9822c..de4792f2ee31f 100644 --- a/docs/design/features/InlineArrayAttribute.md +++ b/docs/design/features/InlineArrayAttribute.md @@ -5,6 +5,8 @@ Our preexisting offering in this area – [unsafe fixed-sized buffers](https://d *The InlineArrayAttribute is a building block to allow efficient, type-safe, overrun-safe indexable/sliceable inline data.* +An example of one such feature is [`Inline Arrays` in C# 12.0](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/inline-arrays.md). + ### API ```C#