Skip to content
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

Proposal: Fixed-size buffer types #1320

Closed
IS4Code opened this issue Feb 11, 2018 · 7 comments
Closed

Proposal: Fixed-size buffer types #1320

IS4Code opened this issue Feb 11, 2018 · 7 comments

Comments

@IS4Code
Copy link

IS4Code commented Feb 11, 2018

Seeing the proposed updates to fixed-sized buffers, I have another proposal – the ability to specify a fixed-size buffer on any storage location and therefore effectively introduce fixed-size buffers to the type system.

At the moment, only fields of structs can be fixed-size buffers:

public struct Buffer
{
	public fixed int buf[256];
}

I propose widening this syntax not just to fields, but also to variables with this alternate syntax possible:

fixed int[256] buf;

This makes buf a variable with type fixed int[256] – a new type syntax that refers to the compiler-generated buffer type. In this case, the compiler-generated type looks like this:

[StructLayout(LayoutKind.Sequential, Size=1024), CompilerGenerated, UnsafeValueType]
public struct <buf>e__FixedBuffer0
{
	public int FixedElementField;
}

Instead of a per-field basis, this type would be generated for every combination of type and size used in the code, and would be shared among all variables that are fixed-size buffer with the same base type and size.

A value of a fixed-size buffer type is effectively a by-value array, and passing it would copy all values:

public static void Method(fixed int buf[256])
{
	buf[0] = 2;
}

Buffer b = new Buffer();
b.buf[0] = 1;
Method(b.buf);
//b.buf[0] is still 1

Instead, a reference to a fixed-size buffer variable must be passed in order to modify its values:

public static void Method(ref fixed int buf[256]) //or (ref fixed int[256] buf), technically similar to Span<int> but with a fixed size
{
	buf[0] = 2;
}

Since the fixed-size buffer is now a type, it can also be used as a return value, or as a property:

public fixed int[256] DefaultBuffer => default(fixed int[256]); //or 'new fixed int[256];'

Creating a new fixed-size buffer this way does not use stackalloc, it simply treats the type as a regular value type. Structurally, the type fixed int[3] is analogous with (int, int, int) (value tuple syntax). No unsafe memory access has to be performed to move a fixed-size buffer since it is just moving a value type.

However, unlike the value tuple type, which is the same in all assemblies, the fixed-size buffer type will be defined for each assembly (module) that uses one. Since all these buffers have the same memory layout, they can be used interchangeably with each other, if an instance of a fixed-size buffer created in the local assembly is to be passed to a method taking a fixed-size buffer (with the same element type and size) defined in a foreign assembly, for example.

In my opinion, fixed-size buffer types go along well with the types already present in C# and are useful in combination with the new .NET memory types (Span<T> etc.). They make moving buffers easier and present a compile-time checking of buffer size.

@HaloFour
Copy link
Contributor

I'm not a fan of any C# proposal which results in a compiler-generated and named type being exposed publicly. It puts a lot of onus on the specification and the compiler to produce a deterministic name and shape to the type so that existing consumers can't break when the code is recompiled.

Aside that, I don't see what this notation buys you. These "structs" can't be passed around. They're useless themselves. They only exist to force the CLR to allocate enough memory to contain the buffer. The buffer itself is managed entirely through unsafe pointer arithmetic and direct memory access. Passing a fixed buffer "by reference" makes no sense. That would be just a pointer.

@IS4Code
Copy link
Author

IS4Code commented Feb 11, 2018

I understand your concerns, but this already happens with standard public fixed-sized buffer fields. Their type must be exposed publicly in order to access the field, so the potential risk of breaking the binding doesn't increase. However, now I see that the naming of these types cannot change, so the types of fixed-size buffer fields cannot be shared and must use the same naming convention.

I think you misunderstand the semantics of the proposed fixed-size buffer type. Passing it "by reference" makes perfect sense, since without the ref keyword, it would pass the whole array by value. I think the following C++ code could illustrate what the type behaves like:

template <class T, size_t Size>
class fixed
{
	T buffer[Size];
};

It's not just typedef T fixed[Size] since that would mean it decays into a pointer if used in a parameter. Fixed-size buffer type is a value and behaves like a value, you can reference it or copy it.

Also see the linked proposal, there a way is proposed to map the individual elements of a fixed-size buffer to fields of the compiler-generated struct. I think this makes the type well defined and usable beyond simply a way to allocate memory. If the CLR can move this memory around or reference it in a managed way, why not allow it do so?

@ygc369
Copy link

ygc369 commented Feb 12, 2018

Support this idea. It's just what I want to say.

@jnm2
Copy link
Contributor

jnm2 commented Feb 12, 2018

@HaloFour Oh yes, I meant to bring up fixed-size buffers in one of our previous conversations as an example of existing public type generation.

@tannergooding
Copy link
Member

I don't think this is worth doing. It is a fairly niche feature that is basically just shortcutting the need to do Span<T> fixedBuffer = stackalloc T[size] before calling your method.

Additionally, since it is a value type and would generally result in a "large" struct, it would (from a performance perspective) be expensive unless you always passed/returned using ref.

The "workaround" for the lack of this feature is also fairly trivial. It only takes a few lines of code to create a named type which uses a fixed-sized buffer under the hood. The unmanaged constraint that is slated for 7.3 will likely make this even easier (in that you could likely just create a generic type for the well-known fixed-buffer sizes you need).

@HaloFour
Copy link
Contributor

@jnm2

Yeah, I wasn't aware that the struct would be made public. Kind of scary as the spec doesn't cover the naming strategy of that struct at all so different compiler implementations could produce incompatible results. IIRC that's the only case where a compiler generated name is leaked publicly like this.

@IS4Code IS4Code changed the title Fixed-size buffer types Proposal: Fixed-size buffer types Feb 24, 2018
@333fred
Copy link
Member

333fred commented Mar 16, 2023

Duplicate of #1314.

@333fred 333fred closed this as not planned Won't fix, can't repro, duplicate, stale Mar 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants