# Params Improvements Working Group Meeting for November 3, 2022
The second meeting of the _`params` improvements_ working group was focused on the code generated for arguments to `params ReadOnlySpan<T>`. 

## Span allocation
The compiler _may allocate a span on the stack_ when:
- the span is implicitly allocated (as a `params` argument or as a [_collection literal_](https://github.com/dotnet/csharplang/issues/5354)),
- the span length is known at compile time, and
- the compiler can determine _through escape analysis_ that no references to the span escape the containing method.

For arguments to a `params ReadOnlySpan<T>` parameter (which is implicitly `scoped`), the conditions above are satisfied.

We have a [request](https://github.com/dotnet/runtime/issues/61135) with the runtime team to support _fixed size arrays_ of managed types, at least for fields.
If we have _fixed size array_ fields, we can define `struct` types with inline arrays and use locals for stack allocated arrays.

For example, consider a `FixedSizeArray2` type defined below which includes an inline 2 element array:
```csharp
struct FixedSizeArray2<T>
{
    public T[2] Array; // pseudo-code for inline array
}
```

With that type, a call to `WriteLine("{0}, {1}", x, y)` could be emitted as:
```csharp
var _tmp1 = new FixedSizeArray2<object>();
_tmp1.Array[0] = x;
_tmp1.Array[1] = y;
var _tmp2 = new ReadOnlySpan<object>(_tmp.Array);

// WriteLine(string format, params ReadOnlySpan<object> args);
WriteLine("{0}, {1}", _tmp2);
```

Ideally the BCL will provide types such as `FixedSizeArray1`, `FixedSizeArray2`, etc. for a limited number of array lengths.
And if the compilation requires spans for other argument lengths, the compiler will generate and emit the additional types.

The number of arguments passed to a `params` parameter is _not considered_ when determining whether to implicitly allocate the span on the stack.
To avoid implicit stack allocation at a call site, the calling code should allocate the array explicitly with `new[] { ... }`.
We believe scenarios where stack allocation regardless of argument length becomes an issue are unlikely, and should be easy to work around, but we can adjust if necessary.

## Span reuse
The compiler _may reuse a span_ when:
- the span is implicitly allocated (as a `params` argument or as a [_collection literal_](https://github.com/dotnet/csharplang/issues/5354)),
- the span length is known at compile time,
- the compiler can determine _through escape analysis_ that no references to the span escape the containing method, and
- the compiler can determine there are _no reachable aliases_ to the span in user code.

For arguments to a `params ReadOnlySpan<T>` parameter (which is implicitly `scoped`), the conditions above are satisfied.

The compiler will reuse a single span across all calls to `params ReadOnlySpan<T>` methods where the span element types are considered identical by the runtime.

The length of the reused span will be the length of the longest `params` argument list from all uses.
(The actual span argument passed to a `params` method will be a slice of the reused span, with the expected length for the call.)

Reuse may be across distinct call sites _or_ repeated calls from the same call site.
Reuse is per thread of execution and within the same method only.
No reuse across _lambda expressions_ and the containing method.
Reusing spans across _local functions_ and the containing method is possible if the local function is not used as a delegate, although the implementation cost of such an optimization may outweigh the benefit.

When exiting a scope, the compiler will ensure that no implicitly allocated span holds references from the scope.

To opt out of reuse at a call site, the calling code should allocate the span explicitly.

_Should we only reuse spans that are allocated on the stack? If we also allow reuse of heap allocated buffers, that will require completely different code gen for managing allocations._

## Collection literals
We will support stack allocation and reuse of spans for _collection literals_.

For collection literals that include a _spread operator_, the length of the resulting span is not known at compile time.
For those cases, we will choose a maximum length for stack allocation and generate code that falls back to heap allocation at runtime if that length is exceeded.
As per the _collection literals_ working group, a hidden diagnostic will be reported in cases where stack allocation may not be possible at runtime.

For instance, the expression `(ReadOnlySpan<int>)[..e]` could be emitted as:
```csharp
var _tmp1 = new FixedSizeArray8<int>();
ReadOnlySpan<int> _tmp2;
if (Enumerable.TryGetNonEnumeratedCount(e, out int n) && n <= 8)
{
    int i = 0;
    foreach (var item in e)
    {
        _tmp1.Array[i++] = item;
        if (i == n) break;
    }
    _tmp2 = new ReadOnlySpan<int>(_tmp1.Array, 0, n);
}
else
    _tmp2 = new ReadOnlySpan<int>(e.ToArray());
```

## Example
Consider the following method with multiple calls to a `params` method:
```csharp
// static void WriteLine(string format, params ReadOnlySpan<object> args);

static void WriteDictionary<K, V>(Dictionary<K, V> dictionary)
{
    WriteLine("Dictionary");

    foreach (var (k, v) in dictionary)
        WriteLine("{0}, {1}", k, v);

    WriteLine("Count = {0}", dictionary.Count);
}
```

The method could be lowered to:
```csharp
static void WriteDictionary<K, V>(Dictionary<K, V> dictionary)
{
    FixedSizeArray2 _tmp1 = new FixedSizeArray2();

    WriteLine("Dictionary",
        new ReadOnlySpan<object>(Array.Empty<object>()); // no reuse

    foreach (var (k, v) in dictionary)
    {
        _tmp1.Array[0] = k;
        _tmp1.Array[1] = v;
        WriteLine("{0}, {1}",
            new ReadOnlySpan(_tmp1.Array)); // reuse
        Array.Clear(_tmp1.Array);           // clear
    }

    _tmp1.Array[0] = dictionary.Count;
    WriteLine("Count = {0}",
        new ReadOnlySpan(_tmp1.Array, 0, 1)); // reuse
}
```
