From 77b0a8ca39e39325941964c9f00607ed29d1bb75 Mon Sep 17 00:00:00 2001 From: RealityProgrammer Date: Tue, 14 Mar 2023 21:18:01 +0700 Subject: [PATCH] Move some intrinsic methods around, reimplement RuneExtensions.Repeat(Rune, Int32) --- X10D.Tests/src/Text/RuneTests.cs | 32 ++++ X10D/Resource.Designer.cs | 11 +- X10D/Resource.resx | 3 + X10D/src/Core/IntrinsicExtensions.cs | 153 ++++++++++++++++++- X10D/src/Core/IntrinsicUtility.cs | 217 +++------------------------ X10D/src/Core/SpanExtensions.cs | 64 ++++---- X10D/src/Text/RuneExtensions.cs | 62 +++++--- 7 files changed, 292 insertions(+), 250 deletions(-) diff --git a/X10D.Tests/src/Text/RuneTests.cs b/X10D.Tests/src/Text/RuneTests.cs index c8181b1..6382fab 100644 --- a/X10D.Tests/src/Text/RuneTests.cs +++ b/X10D.Tests/src/Text/RuneTests.cs @@ -45,6 +45,38 @@ public class RuneTests Assert.AreEqual("a", repeated); } + [TestMethod] + public void RepeatCodepoint_0000_007F_ShouldCorrect() + { + string repeated = new Rune(69).Repeat(16); + Assert.AreEqual(16, repeated.Length); + Assert.AreEqual("EEEEEEEEEEEEEEEE", repeated); + } + + [TestMethod] + public void RepeatCodepoint_0080_07FF_ShouldCorrect() + { + string repeated = new Rune(192).Repeat(8); + Assert.AreEqual(8, repeated.Length); + Assert.AreEqual("ÀÀÀÀÀÀÀÀ", repeated); + } + + [TestMethod] + public void RepeatCodepoint_0800_FFFF_ShouldCorrect() + { + string repeated = new Rune(0x0800).Repeat(5); + Assert.AreEqual(5, repeated.Length); + Assert.AreEqual("ࠀࠀࠀࠀࠀ", repeated); + } + + [TestMethod] + public void RepeatCodepointBeyondU10000ShouldCorrect() + { + string repeated = new Rune('\uD800', '\uDC00').Repeat(6); + Assert.AreEqual(12, repeated.Length); + Assert.AreEqual("𐀀𐀀𐀀𐀀𐀀𐀀", repeated); + } + [TestMethod] public void RepeatZeroCountShouldBeEmpty() { diff --git a/X10D/Resource.Designer.cs b/X10D/Resource.Designer.cs index 02e56e9..9f56c01 100644 --- a/X10D/Resource.Designer.cs +++ b/X10D/Resource.Designer.cs @@ -19,7 +19,7 @@ namespace X10D { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resource { @@ -77,5 +77,14 @@ namespace X10D { return ResourceManager.GetString("EnumParseNotEnumException", resourceCulture); } } + + /// + /// Looks up a localized string similar to Rune.Utf8SequenceLength returns value outside range 1 to 4 (inclusive), which is unexpected according to the official documentation.. + /// + internal static string RuneUtf8SequenceLengthUnexpectedValue { + get { + return ResourceManager.GetString("RuneUtf8SequenceLengthUnexpectedValue", resourceCulture); + } + } } } diff --git a/X10D/Resource.resx b/X10D/Resource.resx index 3a48c16..7ac15f8 100644 --- a/X10D/Resource.resx +++ b/X10D/Resource.resx @@ -123,4 +123,7 @@ Type provided must be an Enum. + + Rune.Utf8SequenceLength returns value outside range 1 to 4 (inclusive), which is unexpected according to the official documentation. + \ No newline at end of file diff --git a/X10D/src/Core/IntrinsicExtensions.cs b/X10D/src/Core/IntrinsicExtensions.cs index 1128360..bcd8cfd 100644 --- a/X10D/src/Core/IntrinsicExtensions.cs +++ b/X10D/src/Core/IntrinsicExtensions.cs @@ -1,6 +1,9 @@ #if NETCOREAPP3_0_OR_GREATER +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; namespace X10D.Core; @@ -10,6 +13,154 @@ namespace X10D.Core; /// public static class IntrinsicExtensions { - // Got nothing for now. + /// + /// + /// Correcting of into 0 and 1 depend on their boolean truthiness. + /// + /// Operation:
+ /// + /// for (int i = 0; i < 8; i++) { + /// dest[i] = vector[i] == 0 ? 0 : 1; + /// } + /// + ///
+ /// Vector of byte to correct. + /// + /// A of which remapped back to 0 and 1 based on boolean truthiness. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Vector64 CorrectBoolean(this Vector64 vector) + { + // TODO: AdvSimd implementation. + // TODO: WasmSimd implementation. (?) + + var output = IntrinsicUtility.GetUninitializedVector64(); + + for (int i = 0; i < Vector64.Count; i++) + { + ref var writeElement = ref Unsafe.Add(ref Unsafe.As, byte>(ref output), i); +#if NET7_0_OR_GREATER + writeElement = vector[i] == 0 ? (byte)0 : (byte)1; +#else + var element = Unsafe.Add(ref Unsafe.As, byte>(ref vector), i); + writeElement = element == 0 ? (byte)0 : (byte)1; +#endif + } + + return output; + } + + /// + /// + /// Correcting of into 0 and 1 depend on their boolean truthiness. + /// + /// Operation:
+ /// + /// for (int i = 0; i < 16; i++) { + /// dest[i] = vector[i] == 0 ? 0 : 1; + /// } + /// + ///
+ /// Vector of byte to correct. + /// + /// A of which remapped back to 0 and 1 based on boolean truthiness. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Vector128 CorrectBoolean(this Vector128 vector) + { + if (Sse2.IsSupported) + { + var cmp = Sse2.CompareEqual(vector, Vector128.Zero); + var result = Sse2.AndNot(cmp, Vector128.Create((byte)1)); + + return result; + } + + // TODO: AdvSimd implementation. + // TODO: WasmSimd implementation. + + var output = IntrinsicUtility.GetUninitializedVector128(); + + for (int i = 0; i < Vector128.Count; i++) + { + Unsafe.Add(ref Unsafe.As, byte>(ref output), i) = + Unsafe.Add(ref Unsafe.As, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1; + } + + return output; + } + + /// + /// + /// Correcting of into 0 and 1 depend on their boolean truthiness. + /// + /// Operation:
+ /// + /// for (int i = 0; i < 32; i++) { + /// dest[i] = vector[i] == 0 ? 0 : 1; + /// } + /// + ///
+ /// Vector of byte to correct. + /// + /// A of which remapped back to 0 and 1 based on boolean truthiness. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Vector256 CorrectBoolean(this Vector256 vector) + { + if (Avx2.IsSupported) + { + var cmp = Avx2.CompareEqual(vector, Vector256.Zero); + var result = Avx2.AndNot(cmp, Vector256.Create((byte)1)); + + return result; + } + + var output = IntrinsicUtility.GetUninitializedVector256(); + + for (int i = 0; i < Vector256.Count; i++) + { + Unsafe.Add(ref Unsafe.As, byte>(ref output), i) = + Unsafe.Add(ref Unsafe.As, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1; + } + + return output; + } + + /// + /// + /// Reverse position of 2 64-bit unsigned integer. + /// + /// Operation:
+ /// + /// dest[1] = vector[0]; + /// dest[0] = vector[1]; + /// + ///
+ /// Input vector. + /// + /// A of with elements the same as input vector except their positions + /// (or indices) are reversed. + /// + [Pure] + [CLSCompliant(false)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Vector128 ReverseElements(this Vector128 vector) + { + if (Sse2.IsSupported) + { + return Sse2.Shuffle(vector.AsDouble(), vector.AsDouble(), 0b01).AsUInt64(); + } + + Vector128 output = IntrinsicUtility.GetUninitializedVector128(); + + Unsafe.As, ulong>(ref output) = Unsafe.Add(ref Unsafe.As, ulong>(ref vector), 1); + Unsafe.Add(ref Unsafe.As, ulong>(ref output), 1) = Unsafe.As, ulong>(ref vector); + + return output; + } } #endif diff --git a/X10D/src/Core/IntrinsicUtility.cs b/X10D/src/Core/IntrinsicUtility.cs index 07dd852..b94edd1 100644 --- a/X10D/src/Core/IntrinsicUtility.cs +++ b/X10D/src/Core/IntrinsicUtility.cs @@ -14,149 +14,14 @@ namespace X10D.Core; public static class IntrinsicUtility { // NOTE: - // ANY METHOD THAT OPERATE ON ANYTHING THAT ISN'T FLOAT IS NOT SSE COMPATIBLE, MUST BE SSE2 AND BEYOND VERSION + // ANY METHOD THAT OPERATE ON ANYTHING THAT ISN'T FLOAT IS NOT SSE COMPATIBLE, MUST BE SSE2 AND BEYONDS // FOR API CONSISTENCY. /// - ///
- /// Correcting of into 0 and 1 depend on their boolean truthiness. - ///
- ///
Operation (raw):
- /// - /// for (int i = 0; i < 8; i++) { - /// dest[i] = ~(vector[i] == 0 ? 0xFF : 0x00) & 1; - /// } - /// - ///
Operation (simplified):
- /// - /// for (int i = 0; i < 8; i++) { - /// dest[i] = vector[i] == 0 ? 0 : 1; - /// } - /// - ///
- /// Vector of byte to correct. - /// - /// A of which remapped back to 0 and 1 based on boolean truthiness. - /// - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static Vector64 CorrectBoolean(Vector64 vector) - { - // TODO: AdvSimd implementation. - // TODO: WasmSimd implementation. (?) - - var output = GetUninitializedVector64(); - - for (int i = 0; i < Vector64.Count; i++) - { - ref var writeElement = ref Unsafe.Add(ref Unsafe.As, byte>(ref output), i); -#if NET7_0_OR_GREATER - writeElement = vector[i] == 0 ? (byte)0 : (byte)1; -#else - var element = Unsafe.Add(ref Unsafe.As, byte>(ref vector), i); - writeElement = element == 0 ? (byte)0 : (byte)1; -#endif - } - - return output; - } - - /// - ///
- /// Correcting of into 0 and 1 depend on their boolean truthiness. - ///
- ///
Operation (raw):
- /// - /// for (int i = 0; i < 16; i++) { - /// dest[i] = ~(vector[i] == 0 ? 0xFF : 0x00) & 1; - /// } - /// - ///
Operation (simplified):
- /// - /// for (int i = 0; i < 16; i++) { - /// dest[i] = vector[i] == 0 ? 0 : 1; - /// } - /// - ///
- /// Vector of byte to correct. - /// - /// A of which remapped back to 0 and 1 based on boolean truthiness. - /// - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static Vector128 CorrectBoolean(Vector128 vector) - { - if (Sse2.IsSupported) - { - var cmp = Sse2.CompareEqual(vector, Vector128.Zero); - var result = Sse2.AndNot(cmp, Vector128.Create((byte)1)); - - return result; - } - - // TODO: AdvSimd implementation. - // TODO: WasmSimd implementation. - - var output = GetUninitializedVector128(); - - for (int i = 0; i < Vector128.Count; i++) - { - Unsafe.Add(ref Unsafe.As, byte>(ref output), i) = - Unsafe.Add(ref Unsafe.As, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1; - } - - return output; - } - - /// - ///
- /// Correcting of into 0 and 1 depend on their boolean truthiness. - ///
- ///
Operation (raw):
- /// - /// for (int i = 0; i < 16; i++) { - /// dest[i] = ~(vector[i] == 0 ? 0xFF : 0x00) & 1; - /// } - /// - ///
Operation (simplified):
- /// - /// for (int i = 0; i < 16; i++) { - /// dest[i] = vector[i] == 0 ? 0 : 1; - /// } - /// - ///
- /// Vector of byte to correct. - /// - /// A of which remapped back to 0 and 1 based on boolean truthiness. - /// - [Pure] - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static Vector256 CorrectBoolean(Vector256 vector) - { - if (Avx2.IsSupported) - { - var cmp = Avx2.CompareEqual(vector, Vector256.Zero); - var result = Avx2.AndNot(cmp, Vector256.Create((byte)1)); - - return result; - } - - var output = GetUninitializedVector256(); - - for (int i = 0; i < Vector256.Count; i++) - { - Unsafe.Add(ref Unsafe.As, byte>(ref output), i) = - Unsafe.Add(ref Unsafe.As, byte>(ref vector), i) == 0 ? (byte)0 : (byte)1; - } - - return output; - } - - /// - ///
+ /// /// Multiply packed 64-bit unsigned integer elements in a and b and truncate the results to 64-bit integer. - ///
- ///
Operation:
+ ///
+ /// Operation:
/// /// dest[0] = lhs[0] * rhs[0]; /// dest[1] = lhs[1] * rhs[1]; @@ -203,10 +68,10 @@ public static class IntrinsicUtility } /// - ///
+ /// /// Multiply packed 64-bit unsigned integer elements in a and b and truncate the results to 64-bit integer. - ///
- ///
Operation:
+ ///
+ /// Operation:
/// /// dest[0] = lhs[0] * rhs[0]; /// dest[1] = lhs[1] * rhs[1]; @@ -252,10 +117,10 @@ public static class IntrinsicUtility } /// - ///
+ /// /// Multiply packed 64-bit signed integer elements in a and b and truncate the results to 64-bit integer. - ///
- ///
Operation:
+ ///
+ /// Operation:
/// /// dest[0] = lhs[0] * rhs[0]; /// dest[1] = lhs[1] * rhs[1]; @@ -274,10 +139,10 @@ public static class IntrinsicUtility } /// - ///
+ /// /// Multiply packed 64-bit signed integer elements in a and b and truncate the results to 64-bit integer. - ///
- ///
Operation:
+ ///
+ /// Operation:
/// /// dest[0] = lhs[0] * rhs[0]; /// dest[1] = lhs[1] * rhs[1]; @@ -298,11 +163,11 @@ public static class IntrinsicUtility } /// - ///
+ /// /// Horizontally apply OR operation on adjacent pairs of single-precision (32-bit) floating-point elements in lhs and /// rhs. - ///
- ///
Operation:
+ ///
+ /// Operation:
/// /// dest[0] = lhs[0] | lhs[1]; /// dest[1] = lhs[2] | lhs[3]; @@ -353,10 +218,10 @@ public static class IntrinsicUtility } /// - ///
+ /// /// Horizontally apply OR operation on adjacent pairs of 32-bit integer elements in lhs and rhs. - ///
- ///
Operation:
+ ///
+ /// Operation:
/// /// dest[0] = lhs[0] | lhs[1]; /// dest[1] = lhs[2] | lhs[3]; @@ -378,10 +243,10 @@ public static class IntrinsicUtility } /// - ///
+ /// /// Horizontally apply OR operation on adjacent pairs of 32-bit unsigned integer elements in lhs and rhs. - ///
- ///
Operation:
+ ///
+ /// Operation:
/// /// dest[0] = lhs[0] | lhs[1]; /// dest[1] = lhs[2] | lhs[3]; @@ -403,41 +268,9 @@ public static class IntrinsicUtility return HorizontalOr(lhs.AsSingle(), rhs.AsSingle()).AsUInt32(); } - /// - ///
Reverse position of 2 64-bit unsigned integer.
- ///
Operation:
- /// - /// ulong tmp = vector[0]; - /// vector[0] = vector[1]; - /// vector[1] = tmp; - /// - ///
- /// Input vector. - /// - /// A of with elements the same as input vector except their positions - /// (or indices) are reversed. - /// - [Pure] - [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static Vector128 ReverseElements(Vector128 vector) - { - if (Sse2.IsSupported) - { - return Sse2.Shuffle(vector.AsDouble(), vector.AsDouble(), 0b01).AsUInt64(); - } - - Vector128 output = GetUninitializedVector128(); - - Unsafe.As, ulong>(ref output) = Unsafe.Add(ref Unsafe.As, ulong>(ref vector), 1); - Unsafe.Add(ref Unsafe.As, ulong>(ref output), 1) = Unsafe.As, ulong>(ref vector); - - return output; - } - // Helper methods [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static Vector64 GetUninitializedVector64() where T : struct + internal static Vector64 GetUninitializedVector64() where T : struct { #if NET6_0_OR_GREATER Unsafe.SkipInit(out Vector64 output); @@ -448,7 +281,7 @@ public static class IntrinsicUtility } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static Vector128 GetUninitializedVector128() where T : struct + internal static Vector128 GetUninitializedVector128() where T : struct { #if NET6_0_OR_GREATER Unsafe.SkipInit(out Vector128 output); @@ -459,7 +292,7 @@ public static class IntrinsicUtility } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static Vector256 GetUninitializedVector256() where T : struct + internal static Vector256 GetUninitializedVector256() where T : struct { #if NET6_0_OR_GREATER Unsafe.SkipInit(out Vector256 output); diff --git a/X10D/src/Core/SpanExtensions.cs b/X10D/src/Core/SpanExtensions.cs index e4ecf07..77ecede 100644 --- a/X10D/src/Core/SpanExtensions.cs +++ b/X10D/src/Core/SpanExtensions.cs @@ -173,9 +173,8 @@ public static class SpanExtensions if (Sse2.IsSupported) { var load = Sse2.LoadScalarVector128((ulong*)pSource).AsByte(); - var correct = IntrinsicUtility.CorrectBoolean(load); - return unchecked((byte)(IntegerPackingMagic * correct.AsUInt64().GetElement(0) >> 56)); + return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56)); } // Probably should remove this piece of code because it is untested, but I see no reason why it should fail @@ -185,14 +184,11 @@ public static class SpanExtensions { // Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware). var load = AdvSimd.LoadVector64((byte*)pSource); - var correct = IntrinsicUtility.CorrectBoolean(load); - return unchecked((byte)(IntegerPackingMagic * correct.AsUInt64().GetElement(0) >> 56)); - } - else - { - goto default; + return unchecked((byte)(IntegerPackingMagic * load.CorrectBoolean().AsUInt64().GetElement(0) >> 56)); } + + goto default; } #endif default: @@ -252,10 +248,10 @@ public static class SpanExtensions { fixed (bool* pSource = source) { - var load = Sse2.LoadVector128((byte*)pSource); - var correct = IntrinsicUtility.CorrectBoolean(load).AsUInt64(); - var multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct); - var shift = Sse2.ShiftRightLogical(multiply, 56); + Vector128 load = Sse2.LoadVector128((byte*)pSource); + Vector128 correct = load.CorrectBoolean().AsUInt64(); + Vector128 multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct); + Vector128 shift = Sse2.ShiftRightLogical(multiply, 56); return (short)(shift.GetElement(0) | (shift.GetElement(1) << 8)); } @@ -319,52 +315,52 @@ public static class SpanExtensions { if (Avx2.IsSupported) { - var load = Avx.LoadVector256((byte*)pSource); - var correct = IntrinsicUtility.CorrectBoolean(load).AsUInt64(); + Vector256 load = Avx.LoadVector256((byte*)pSource); + Vector256 correct = load.CorrectBoolean().AsUInt64(); - var multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV256, correct); - var shift = Avx2.ShiftRightLogical(multiply, 56); + Vector256 multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV256, correct); + Vector256 shift = Avx2.ShiftRightLogical(multiply, 56); shift = Avx2.ShiftLeftLogicalVariable(shift, Vector256.Create(0UL, 8, 16, 24)); - var p1 = Avx2.Permute4x64(shift, 0b10_11_00_01); - var or1 = Avx2.Or(shift, p1); - var p2 = Avx2.Permute4x64(or1, 0b00_00_10_10); - var or2 = Avx2.Or(or1, p2); + Vector256 p1 = Avx2.Permute4x64(shift, 0b10_11_00_01); + Vector256 or1 = Avx2.Or(shift, p1); + Vector256 p2 = Avx2.Permute4x64(or1, 0b00_00_10_10); + Vector256 or2 = Avx2.Or(or1, p2); return (int)or2.GetElement(0); } if (Sse2.IsSupported) { - var load = Sse2.LoadVector128((byte*)pSource); - var correct = IntrinsicUtility.CorrectBoolean(load).AsUInt64(); + Vector128 load = Sse2.LoadVector128((byte*)pSource); + Vector128 correct = load.CorrectBoolean().AsUInt64(); - var multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct); - var shift1 = Sse2.ShiftRightLogical(multiply, 56); + Vector128 multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct); + Vector128 shift1 = Sse2.ShiftRightLogical(multiply, 56); shift1 = Sse2.ShiftLeftLogical(shift1, Vector128.Create(0UL, 8UL)); load = Sse2.LoadVector128((byte*)(pSource + 16)); - correct = IntrinsicUtility.CorrectBoolean(load).AsUInt64(); + correct = load.CorrectBoolean().AsUInt64(); multiply = IntrinsicUtility.Multiply(IntegerPackingMagicV128, correct); - var shift2 = Sse2.ShiftRightLogical(multiply, 56); + Vector128 shift2 = Sse2.ShiftRightLogical(multiply, 56); shift2 = Sse2.ShiftLeftLogical(shift2, Vector128.Create(16UL, 24UL)); - var or1 = Sse2.Or(shift1, shift2); - var or2 = Sse2.Or(or1, IntrinsicUtility.ReverseElements(or1)); + Vector128 or1 = Sse2.Or(shift1, shift2); + Vector128 or2 = Sse2.Or(or1, or1.ReverseElements()); return (int)or2.GetElement(0); } if (AdvSimd.IsSupported) { // Hasn't been tested since March 6th 2023 (Reason: Unavailable hardware). - var vector1 = IntrinsicUtility.CorrectBoolean(AdvSimd.LoadVector128((byte*)pSource)).AsUInt64(); - var vector2 = IntrinsicUtility.CorrectBoolean(AdvSimd.LoadVector128((byte*)(pSource + 16))).AsUInt64(); + Vector128 vector1 = AdvSimd.LoadVector128((byte*)pSource).CorrectBoolean().AsUInt64(); + Vector128 vector2 = AdvSimd.LoadVector128((byte*)(pSource + 16)).CorrectBoolean().AsUInt64(); - var calc1 = AdvSimd.ShiftRightLogical(IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector1), 56); - var calc2 = AdvSimd.ShiftRightLogical(IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector2), 56); + Vector128 calc1 = AdvSimd.ShiftRightLogical(IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector1), 56); + Vector128 calc2 = AdvSimd.ShiftRightLogical(IntrinsicUtility.Multiply(IntegerPackingMagicV128, vector2), 56); - var shift1 = AdvSimd.ShiftLogical(calc1, Vector128.Create(0, 8)); - var shift2 = AdvSimd.ShiftLogical(calc2, Vector128.Create(16, 24)); + Vector128 shift1 = AdvSimd.ShiftLogical(calc1, Vector128.Create(0, 8)); + Vector128 shift2 = AdvSimd.ShiftLogical(calc2, Vector128.Create(16, 24)); return (int)(shift1.GetElement(0) | shift1.GetElement(1) | shift2.GetElement(0) | shift2.GetElement(1)); } diff --git a/X10D/src/Text/RuneExtensions.cs b/X10D/src/Text/RuneExtensions.cs index 7bb1684..0089e83 100644 --- a/X10D/src/Text/RuneExtensions.cs +++ b/X10D/src/Text/RuneExtensions.cs @@ -1,5 +1,5 @@ #if NETCOREAPP3_0_OR_GREATER -using System; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -47,40 +47,58 @@ public static class RuneExtensions } // Helpful documentation: https://en.wikipedia.org/wiki/UTF-8 + // This probably gonna break interning but whatever. switch (value.Utf8SequenceLength) { case 1: { - Unsafe.SkipInit(out byte bytes); - value.EncodeToUtf8(MemoryMarshal.CreateSpan(ref bytes, 1)); - + // Codepoint 0 to 0x00FF can be directly turn into char value without any conversion. return new string((char)value.Value, count); } + // Codepoint 0x0080 to 0x07FF takes 2 UTF-8 bytes, and it can be represented by 1 UTF-16 character (.NET runtime use + // UTF-16 encoding). + // Source: https://stackoverflow.com/questions/63905684 case 2: + // Codepoint 0x0800 to 0xFFFF takes 3 UTF-8 bytes, and can also be represented by 1 UTF-16 character. + case 3: { - Span bytes = stackalloc byte[2]; - value.EncodeToUtf8(bytes); + // Codepoint 0x0080 to 0x07FF convert into 1 .NET character string, directly use string constructor. + unsafe + { + Span bytes = stackalloc byte[value.Utf8SequenceLength]; + value.EncodeToUtf8(bytes); - return new string(Encoding.UTF8.GetString(bytes)[0], count); + char character; + Encoding.UTF8.GetChars(bytes, new Span(&character, 1)); + + return new string(character, count); + } + } + + // Codepoint 0x10000 and beyond will takes **only** 2 UTF-16 character. + case 4: + { + return string.Create(count * 2, value, (span, rune) => + { + unsafe { + Span bytes = stackalloc byte[4]; + value.EncodeToUtf8(bytes); + + int characters; // 2 characters, fit inside 1 32-bit integer. + Encoding.UTF8.GetChars(bytes, new Span(&characters, 2)); + + MemoryMarshal.Cast(span).Fill(characters); + } + }); } default: - { - int utf8SequenceLength = value.Utf8SequenceLength; - Span utf8 = stackalloc byte[utf8SequenceLength]; - value.EncodeToUtf8(utf8); - - // Limit to maximum 1024 bytes stack allocation (Rune.Utf8SequenceLength return value in range of [1; 4]) - Span buffer = count <= 256 ? stackalloc byte[utf8.Length * count] : new byte[utf8.Length * count]; - - for (var index = 0; index < count; index++) - { - utf8.CopyTo(buffer.Slice(index * utf8.Length, utf8.Length)); - } - - return Encoding.UTF8.GetString(buffer); - } +#if NET7_0_OR_GREATER + throw new UnreachableException(Resource.RuneUtf8SequenceLengthUnexpectedValue); +#else + throw new InvalidOperationException(Resource.RuneUtf8SequenceLengthUnexpectedValue); +#endif } } }