Description
Compare dotnet/csharplang#5295 and dotnet/roslyn#24621, ultimately subsumed by dotnet/csharplang#5354.
I propose that we add compiler support for translating
let data = ReadOnlySpan [|constant; values|]
let data = ReadOnlySpan "abc123"B
=
let data = new ReadOnlySpan<_> [|constant; values|]
let data = new ReadOnlySpan<byte> "abc123"B
and/or
let data = [|constant; values|].AsSpan ()
let data = "abc123"B.AsSpan ()
and/or, potentially (this would imply some kind of invocation of op_Implicit
or else type-direction à la #1086)
let data : ReadOnlySpan<_> = [|constant; values|]
let data : ReadOnlySpan<byte> = "abc123"B
to non-allocating code that bakes the constant data into the assembly as a binary blob and creates a ReadOnlySpan
at runtime that points to that data.
C# has done this for a while for
public static ReadOnlySpan<byte> Data => new byte[] { constant, values };
and now does it for collection expressions as well (SharpLab):
public static ReadOnlySpan<byte> Bytes => [1, 2, 3];
public static byte M() {
ReadOnlySpan<byte> bytes = [1, 2, 3];
return bytes[1];
}
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
public class C
{
public unsafe static ReadOnlySpan<byte> Bytes
{
get
{
return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref <PrivateImplementationDetails>.039058C6F2C0CB492C533B0A4D14EF77CC0F78ABCCCED5287D84A1A2011CFB81), 3);
}
}
public unsafe static byte M()
{
return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref <PrivateImplementationDetails>.039058C6F2C0CB492C533B0A4D14EF77CC0F78ABCCCED5287D84A1A2011CFB81), 3)[1];
}
}
[CompilerGenerated]
internal sealed class <PrivateImplementationDetails>
{
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 3)]
private struct __StaticArrayInitTypeSize=3
{
}
internal static readonly __StaticArrayInitTypeSize=3 039058C6F2C0CB492C533B0A4D14EF77CC0F78ABCCCED5287D84A1A2011CFB81/* Not supported: data(01 02 03) */;
}
The F# compiler is essentially already halfway there for statically-initialized arrays — it emits static blobs holding the data, but a new array is created and initialized with the data from the blob at runtime (SharpLab).
let data = [|1uy; 2uy; 3uy|]
let f () =
let data = [|1uy; 2uy; 3uy|]
data[1]
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using <StartupCode$_>;
using Microsoft.FSharp.Core;
[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyVersion("0.0.0.0")]
[CompilationMapping(SourceConstructFlags.Module)]
public static class @_
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static <PrivateImplementationDetails$_>.T5291936_3Bytes@ field5291937@/* Not supported: data(01 02 03) */;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static <PrivateImplementationDetails$_>.T5291936_3Bytes@ field5291938@/* Not supported: data(01 02 03) */;
[CompilationMapping(SourceConstructFlags.Value)]
public static byte[] data
{
get
{
return $_.data@3;
}
}
public static byte f()
{
byte[] array = new byte[3];
RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
return array[1];
}
static _()
{
$_.init@ = 0;
int init@ = $_.init@;
}
}
namespace <StartupCode$_>
{
internal static class $_
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly byte[] data@3;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
[DebuggerNonUserCode]
internal static int init@;
static $_()
{
byte[] array = new byte[3];
RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/);
data@3 = array;
}
}
}
internal static class <PrivateImplementationDetails$_>
{
[StructLayout(LayoutKind.Explicit, Size = 3)]
internal struct T5291936_3Bytes@
{
}
}
The proposal here is to update the compiler to emit code for accessing the static data directly via a ReadOnlySpan
, with no runtime array allocation or initialization, when the static array is directly bound or consumed as a ReadOnlySpan
.
Pros and Cons
The advantages of making this adjustment to F# are
- It adds a super-efficient way to access constant collections of data.
The disadvantages of making this adjustment to F# are
- It doesn't seem at first glance that this would have any ill effects. For example, making this change work for array literals
[|…|]
doesn't preclude later making it also work with type-directed[…]
. All the rules about how the resulting span can be used are already in place and would still apply.
Extra information
Estimated cost (XS, S, M, L, XL, XXL):
- S, unless we wanted to get into Type-directed resolution of [ .. ] syntax #1086 territory, in which case XL+...
Related suggestions:
- Potentially Type-directed resolution of [ .. ] syntax #1086.
Affidavit (please submit!)
Please tick these items by placing a cross in the box:
- This is not a question (e.g. like one you might ask on StackOverflow) and I have searched StackOverflow for discussions of this issue
- This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to the compiler and tooling repository
- This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it
- I have searched both open and closed suggestions on this site and believe this is not a duplicate
Please tick all that apply:
- This is not a breaking change to the F# language design
- I or my company would be willing to help implement and/or test this
For Readers
If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.