Skip to content

feat: add Span<T> params to C# struct Create methods for zero-allocation stackalloc support#8970

Open
statxc wants to merge 6 commits intogoogle:masterfrom
statxc:csharp/span-params-create-struct
Open

feat: add Span<T> params to C# struct Create methods for zero-allocation stackalloc support#8970
statxc wants to merge 6 commits intogoogle:masterfrom
statxc:csharp/span-params-create-struct

Conversation

@statxc
Copy link
Contributor

@statxc statxc commented Mar 11, 2026

Summary

  • Add Span<T> parameters to C# struct CreateXXX methods under #if ENABLE_SPAN_T, enabling stackalloc for zero-GC struct creation
  • Flatten multi-dimensional array parameters (T[,]) to 1D Span<T> with computed indices for nested struct arrays
  • Backward compatible: existing code passing T[] still compiles since arrays implicitly convert to Span<T>

Changes

  • src/idl_gen_csharp.cpp: Add Span<T> codegen path with StructHasArrayFields() helper and flat indexing support
  • tests/MyGame/Example/{NestedStruct,ArrayStruct,LargeArrayStruct}.cs: Regenerated via flatc
  • tests/FlatBuffers.Test/FlatBuffersExampleTests.cs: Updated test for flat array params under ENABLE_SPAN_T
  • tests/FlatBuffers.Test/FlatBuffersFixedLengthArrayTests.cs: Added stackalloc test demonstrating zero-allocation usage

How to test

# 1. Build flatc
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -B build && cmake --build build --target flatc -j

# 2. Regenerate C# files
cd tests && ../build/flatc --csharp --gen-object-api --cs-gen-json-serializer --gen-mutable arrays_test.fbs && cd ..

# 3. Run tests (default)
dotnet test tests/FlatBuffers.Test/FlatBuffers.Test.csproj

# 4. Run tests (unsafe byte buffer)
dotnet test tests/FlatBuffers.Test/FlatBuffers.Test.csproj -p:DefineConstants=UNSAFE_BYTEBUFFER

# 5. Run tests (Span<T> + unsafe byte buffer)
dotnet test tests/FlatBuffers.Test/FlatBuffers.Test.csproj -p:DefineConstants="ENABLE_SPAN_T%3BUNSAFE_BYTEBUFFER"

Test plan

  • Default build: 156 tests passed
  • UNSAFE_BYTEBUFFER=true: 156 tests passed
  • ENABLE_SPAN_T=true + UNSAFE_BYTEBUFFER=true: 156 tests passed
  • Regenerated C# files match flatc output (zero diff)

Closes #8927

@statxc statxc requested a review from dbaileychess as a code owner March 11, 2026 13:24
@github-actions github-actions bot added c# c++ codegen Involving generating code from schema labels Mar 11, 2026
@statxc
Copy link
Contributor Author

statxc commented Mar 11, 2026

@jtdavis777 Could you please review this PR? I'd appreciate any feedbacks. Thanks!

@ODtian
Copy link

ODtian commented Mar 11, 2026

@statxc In practice, it is better to use ReadOnlySpan<T> as the parameter type when read-only access is intended, as it enforces the principle of least privilege. BTW, there is no need to provide a separate Span<T> overload because Span<T> implicitly converts to ReadOnlySpan<T>

@statxc
Copy link
Contributor Author

statxc commented Mar 11, 2026

@statxc In practice, it is better to use ReadOnlySpan<T> as the parameter type when read-only access is intended, as it enforces the principle of least privilege. BTW, there is no need to provide a separate Span<T> overload because Span<T> implicitly converts to ReadOnlySpan<T>

Yes. Good point! 100% right. Our Create methods only read from the array parameters - they never write to them. So the parameter type should be ReadOnlySpan instead of Span. Thanks @ODtian 👍. I will update soon

@statxc
Copy link
Contributor Author

statxc commented Mar 11, 2026

@ODtian I updated all! Could you review again? thanks

@ODtian
Copy link

ODtian commented Mar 12, 2026

@statxc The rest looks good to me. I'm not very familiar with FlatBuffers itself though, so it'd be best to have someone with more expertise take a look.

@statxc
Copy link
Contributor Author

statxc commented Mar 12, 2026

@jtdavis777 could you please review this PR? Welcome to any feedbacks

@statxc
Copy link
Contributor Author

statxc commented Mar 17, 2026

@jtdavis777 Sorry for ping again. Any updates for me, please

@statxc
Copy link
Contributor Author

statxc commented Mar 19, 2026

@jtdavis777 I saw your post in the discussion (#8984). Thanks for your passion - I'm really glad to see this community.
I also noticed you’ve been reviewing C/C# changes lately, so I hope you could take a look at this PR as well when you have time. I’d really appreciate any feedback. Thanks again!

@jtdavis777
Copy link
Collaborator

I will admit I know very little about C# (sorry to all the Microsoft Java fans out there) but know that I see you and will help as best I can!

@statxc
Copy link
Contributor Author

statxc commented Mar 20, 2026

I hope someone expert can review this PR as well.

@bigjt-dev
Copy link
Contributor

I like this PR.

I'm not sure I qualify as an expert, but I've spent a decent amount of time working with FlatBuffers and minimizing heap allocations in C#.

  1. I don't see any FlatBuffer encoding problems with these changes.

  2. As you stated, the implicit span conversions help keep the CreateXxx functions mostly compatible with the exception of the 2D arrays. The signature change may cause some mild annoyance for existing user of the API.

If there is an instance where a 2D array is needed, when array_cnt > 0, you could instead trigger the creation of an overloaded CreateXxx that accepts the array types. Then you're covered with backward compatibility and you can avoid changes to Object API Pack().

  1. ENABLE_SPAN_T

My take is that ENABLE_SPAN_T needs to be paired with UNSAFE_BYTEBUFFER given the following warning in the runtime code:

#if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER
#warning ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined
#endif

Consider adding UNSAFE_BYTEBUFFE to your #ifs.

Other thoughts:

Now that you're using spans, FlatBufferBuilder Put can be used reduce for loops (flattened 2d operations could just 'Slice' each row and Put()). However, those Put functions don't do endian swapping per span element, chance of unexpected breaks for big endian users.

@statxc statxc force-pushed the csharp/span-params-create-struct branch from f673e3d to fee49cc Compare March 23, 2026 00:16
@statxc
Copy link
Contributor Author

statxc commented Mar 23, 2026

@bigjt-dev
I have changed the Create method generation from #if/#else/#endif (replace) to always emitting the original first, then adding the Span overload under #if ENABLE_SPAN_T. Removed all #if guards from Pack methods and deleted the now-unnecessary GenPackDeclArray helper.

@bigjt-dev
Copy link
Contributor

Nice, LGTM.

I did pull your changes to see how the param types would resolve. I saw this test was failing: FixedLengthArray_CreateWithStackalloc_ReturnTrue. The nestedStruct.__assign needs fixed in that test plus a few other call sites external to your changes.

I'd change to: nestedStruct.__assign(buffer.GetInt(buffer.Position) + buffer.Position, buffer); to get to the right spot.

@statxc
Copy link
Contributor Author

statxc commented Mar 25, 2026

@jtdavis777 @bigjt-dev ready to merge now. please review again. thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c# c++ codegen Involving generating code from schema

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Span params for CreateXXX in C#

4 participants