diff --git a/src/FSharp.Core/FSharp.Core.fsproj b/src/FSharp.Core/FSharp.Core.fsproj
index f1777c72b39..65ca6c36899 100644
--- a/src/FSharp.Core/FSharp.Core.fsproj
+++ b/src/FSharp.Core/FSharp.Core.fsproj
@@ -147,6 +147,12 @@
Collections/seq.fs
+
+ NativeInterop/nativeptr.fsi
+
+
+ NativeInterop/nativeptr.fs
+
Collections/string.fsi
@@ -213,12 +219,6 @@
Quotations/quotations.fs
-
- NativeInterop/nativeptr.fsi
-
-
- NativeInterop/nativeptr.fs
-
Control/event.fsi
diff --git a/src/FSharp.Core/string.fs b/src/FSharp.Core/string.fs
index 800fcdd7826..73052a5c25d 100644
--- a/src/FSharp.Core/string.fs
+++ b/src/FSharp.Core/string.fs
@@ -8,8 +8,11 @@ open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators
open Microsoft.FSharp.Core.Operators
open Microsoft.FSharp.Core.Operators.Checked
open Microsoft.FSharp.Collections
+open Microsoft.FSharp.NativeInterop
open Microsoft.FSharp.Primitives.Basics
+#nowarn "9" // Uses of this construct may result in the generation of unverifiable .NET IL code
+
[]
[]
module String =
@@ -19,6 +22,11 @@ module String =
[]
let LOH_CHAR_THRESHOLD = 40_000
+#if NETSTANDARD2_1_OR_GREATER
+ []
+ let STACKALLOC_THRESHOLD = 512
+#endif
+
[]
let length (str: string) =
if isNull str then 0 else str.Length
@@ -54,19 +62,39 @@ module String =
for i = 0 to str.Length - 1 do
f.Invoke(i, str.[i])
+#if NETSTANDARD2_1_OR_GREATER
+ // Cache SpanAction instance to avoid allocations
+ let private _mapAction =
+ System.Buffers.SpanAction char))>
+ (fun (result: Span) (struct (str: string, mapping: char -> char)) ->
+ for i = 0 to result.Length - 1 do
+ result[i] <- mapping str[i])
+#endif
+
[]
let map (mapping: char -> char) (str: string) =
if String.IsNullOrEmpty str then
String.Empty
else
+#if NETSTANDARD2_1_OR_GREATER
+ String.Create(str.Length, struct (str, mapping), _mapAction)
+#else
let result = str.ToCharArray()
- let mutable i = 0
- for c in result do
- result.[i] <- mapping c
- i <- i + 1
+ for i = 0 to result.Length - 1 do
+ result[i] <- mapping result[i]
String(result)
+#endif
+
+#if NETSTANDARD2_1_OR_GREATER
+ // Cache SpanAction instance to avoid allocations
+ let private _mapiAction =
+ System.Buffers.SpanAction)>
+ (fun (result: Span) (struct (str, mapping)) ->
+ for i = 0 to result.Length - 1 do
+ result[i] <- mapping.Invoke(i, str[i]))
+#endif
[]
let mapi (mapping: int -> char -> char) (str: string) =
@@ -75,19 +103,21 @@ module String =
if len = 0 then
String.Empty
else
- let result = str.ToCharArray()
let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(mapping)
+#if NETSTANDARD2_1_OR_GREATER
+ String.Create(len, struct (str, f), _mapiAction)
+#else
+ let result = str.ToCharArray()
- let mutable i = 0
-
- while i < len do
- result.[i] <- f.Invoke(i, result.[i])
- i <- i + 1
+ for i = 0 to result.Length - 1 do
+ result[i] <- f.Invoke(i, result[i])
String(result)
+#endif
[]
let filter (predicate: char -> bool) (str: string) =
+
let len = length str
if len = 0 then
@@ -107,8 +137,18 @@ module String =
res.ToString()
else
- // Must do it this way, since array.fs is not yet in scope, but this is safe
- let target = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len
+
+ let target =
+#if NETSTANDARD2_1_OR_GREATER
+ if len <= STACKALLOC_THRESHOLD then
+ Span((NativePtr.toVoidPtr (NativePtr.stackalloc STACKALLOC_THRESHOLD)), len)
+ else
+ // Using the primitive, because array.fs is not yet in scope. It's safe: both len and count are positive.
+ Span(Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len)
+#else
+ // same as above
+ Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked len
+#endif
let mutable i = 0
for c in str do
@@ -116,7 +156,11 @@ module String =
target.[i] <- c
i <- i + 1
+#if NETSTANDARD2_1_OR_GREATER
+ String(target.Slice(0, i))
+#else
String(target, 0, i)
+#endif
[]
let collect (mapping: char -> string) (str: string) =
@@ -139,6 +183,26 @@ module String =
res.ToString()
+#if NETSTANDARD2_1_OR_GREATER
+ let _replicateAction =
+ System.Buffers.SpanAction(fun (target: Span) (str: string) ->
+ let len = str.Length
+ let source = str.AsSpan()
+
+ // O(log(n)) performance loop:
+ // Copy first string, then keep copying what we already copied
+ // (i.e., doubling it) until we reach or pass the halfway point
+ source.CopyTo(target)
+ let mutable i = len
+
+ while i * 2 < target.Length do
+ target.Slice(0, i).CopyTo(target.Slice(i, i))
+ i <- i * 2
+
+ // finally, copy the remaining half, or less-then half
+ target.Slice(0, target.Length - i).CopyTo(target.Slice(i, target.Length - i)))
+#endif
+
[]
let replicate (count: int) (str: string) =
if count < 0 then
@@ -160,6 +224,9 @@ module String =
| _ -> String.Concat(str, str, str, str)
else
+#if NETSTANDARD2_1_OR_GREATER
+ String.Create(len * count, str, _replicateAction)
+#else
// Using the primitive, because array.fs is not yet in scope. It's safe: both len and count are positive.
let target =
Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked (len * count)
@@ -176,9 +243,10 @@ module String =
Array.Copy(target, 0, target, i, i)
i <- i * 2
- // finally, copy the remain half, or less-then half
+ // finally, copy the remaining half, or less-than half
Array.Copy(target, 0, target, i, target.Length - i)
String(target)
+#endif
[]
let forall predicate (str: string) =