From 7cf2e7f2abffe767a2de9a1eb39205823e3fbab7 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Thu, 19 Sep 2024 00:16:58 +0800 Subject: [PATCH 1/7] fix: unalignment load in ReadOnlySpan.NotZero --- .gitignore | 2 ++ src/Neo.VM/Types/ByteString.cs | 2 +- src/Neo.VM/Unsafe.cs | 42 -------------------------------- src/Neo.VM/Utility.cs | 17 +++++++++++++ tests/Neo.VM.Tests/UT_Unsafe.cs | 33 ------------------------- tests/Neo.VM.Tests/UT_Utility.cs | 17 +++++++++++++ 6 files changed, 37 insertions(+), 76 deletions(-) delete mode 100644 src/Neo.VM/Unsafe.cs delete mode 100644 tests/Neo.VM.Tests/UT_Unsafe.cs diff --git a/.gitignore b/.gitignore index 1358af3bde..214203c9c3 100644 --- a/.gitignore +++ b/.gitignore @@ -257,3 +257,5 @@ paket-files/ PublishProfiles /.vscode launchSettings.json +/coverages +**/.DS_Store \ No newline at end of file diff --git a/src/Neo.VM/Types/ByteString.cs b/src/Neo.VM/Types/ByteString.cs index 2d2df2d862..4a669dbfd5 100644 --- a/src/Neo.VM/Types/ByteString.cs +++ b/src/Neo.VM/Types/ByteString.cs @@ -85,7 +85,7 @@ internal bool Equals(StackItem? other, ref uint limits) public override bool GetBoolean() { if (Size > Integer.MaxSize) throw new InvalidCastException(); - return Unsafe.NotZero(GetSpan()); + return GetSpan().NotZero(); } public override int GetHashCode() diff --git a/src/Neo.VM/Unsafe.cs b/src/Neo.VM/Unsafe.cs deleted file mode 100644 index e3f4366be2..0000000000 --- a/src/Neo.VM/Unsafe.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// Unsafe.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using System; -using System.Runtime.CompilerServices; - -namespace Neo.VM -{ - unsafe internal static class Unsafe - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool NotZero(ReadOnlySpan x) - { - int len = x.Length; - if (len == 0) return false; - fixed (byte* xp = x) - { - long* xlp = (long*)xp; - for (; len >= 8; len -= 8) - { - if (*xlp != 0) return true; - xlp++; - } - byte* xbp = (byte*)xlp; - for (; len > 0; len--) - { - if (*xbp != 0) return true; - xbp++; - } - } - return false; - } - } -} diff --git a/src/Neo.VM/Utility.cs b/src/Neo.VM/Utility.cs index 7c1852b7f9..311589a16a 100644 --- a/src/Neo.VM/Utility.cs +++ b/src/Neo.VM/Utility.cs @@ -27,6 +27,23 @@ static Utility() StrictUTF8.EncoderFallback = EncoderFallback.ExceptionFallback; } + public static bool NotZero(this ReadOnlySpan x) + { + int i; + for (i = 0; i + 7 < x.Length; i += 8) + { + if ((x[i] | x[i + 1] | x[i + 2] | x[i + 3] | x[i + 4] | x[i + 5] | x[i + 6] | x[i + 7]) != 0) + return true; + } + + for (; i < x.Length; i++) + { + if (x[i] != 0) return true; + } + + return false; + } + public static bool TryGetString(this ReadOnlySpan bytes, out string? value) { try diff --git a/tests/Neo.VM.Tests/UT_Unsafe.cs b/tests/Neo.VM.Tests/UT_Unsafe.cs deleted file mode 100644 index e3b4b8708f..0000000000 --- a/tests/Neo.VM.Tests/UT_Unsafe.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// UT_Unsafe.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.VM; - -namespace Neo.Test -{ - [TestClass] - public class UT_Unsafe - { - [TestMethod] - public void TestNotZero() - { - Assert.IsFalse(Unsafe.NotZero(System.Array.Empty())); - Assert.IsFalse(Unsafe.NotZero(new byte[4])); - Assert.IsFalse(Unsafe.NotZero(new byte[8])); - Assert.IsFalse(Unsafe.NotZero(new byte[11])); - - Assert.IsTrue(Unsafe.NotZero(new byte[4] { 0x00, 0x00, 0x00, 0x01 })); - Assert.IsTrue(Unsafe.NotZero(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 })); - Assert.IsTrue(Unsafe.NotZero(new byte[11] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 })); - } - } -} diff --git a/tests/Neo.VM.Tests/UT_Utility.cs b/tests/Neo.VM.Tests/UT_Utility.cs index e87bcbcda7..758c6ac100 100644 --- a/tests/Neo.VM.Tests/UT_Utility.cs +++ b/tests/Neo.VM.Tests/UT_Utility.cs @@ -101,5 +101,22 @@ public void TestModInverseTest() Assert.AreEqual(new BigInteger(52), new BigInteger(19).ModInverse(141)); } + + [TestMethod] + public void TestNotZero() + { + Assert.IsFalse(new ReadOnlySpan(System.Array.Empty()).NotZero()); + Assert.IsFalse(new ReadOnlySpan(new byte[4]).NotZero()); + Assert.IsFalse(new ReadOnlySpan(new byte[7]).NotZero()); + Assert.IsFalse(new ReadOnlySpan(new byte[8]).NotZero()); + Assert.IsFalse(new ReadOnlySpan(new byte[9]).NotZero()); + Assert.IsFalse(new ReadOnlySpan(new byte[11]).NotZero()); + + Assert.IsTrue(new ReadOnlySpan(new byte[4] { 0x00, 0x00, 0x00, 0x01 }).NotZero()); + Assert.IsTrue(new ReadOnlySpan(new byte[7] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }).NotZero()); + Assert.IsTrue(new ReadOnlySpan(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }).NotZero()); + Assert.IsTrue(new ReadOnlySpan(new byte[9] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }).NotZero()); + Assert.IsTrue(new ReadOnlySpan(new byte[11] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }).NotZero()); + } } } From 37f78c8b19249c98fafac3f3039bff2eb8b801be Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 18 Sep 2024 21:23:12 +0200 Subject: [PATCH 2/7] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 214203c9c3..5bf30d8fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -258,4 +258,4 @@ PublishProfiles /.vscode launchSettings.json /coverages -**/.DS_Store \ No newline at end of file +**/.DS_Store From 203146070744eed6e15ac69e46e7f311f8af7c7a Mon Sep 17 00:00:00 2001 From: nan01ab Date: Thu, 19 Sep 2024 22:21:12 +0800 Subject: [PATCH 3/7] use SequenceEqual to compare bytes for better performace --- src/Neo.VM/Utility.cs | 25 ++++++++++++++++++------- tests/Neo.VM.Tests/UT_Utility.cs | 14 ++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Neo.VM/Utility.cs b/src/Neo.VM/Utility.cs index 311589a16a..b1695a708a 100644 --- a/src/Neo.VM/Utility.cs +++ b/src/Neo.VM/Utility.cs @@ -20,6 +20,11 @@ public static class Utility { public static Encoding StrictUTF8 { get; } + /// + /// A zero byte array. Length is 32 because it compares to short bytes now. + /// + private static readonly byte[] Zeros = new byte[32]; + static Utility() { StrictUTF8 = (Encoding)Encoding.UTF8.Clone(); @@ -27,18 +32,24 @@ static Utility() StrictUTF8.EncoderFallback = EncoderFallback.ExceptionFallback; } + /// + /// All bytes are zero or not in a byte array + /// + /// The byte array + /// true if all bytes are zero, otherwise it returns false + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool NotZero(this ReadOnlySpan x) { - int i; - for (i = 0; i + 7 < x.Length; i += 8) + var zeros = Zeros.AsSpan(); + for (; x.Length > 0;) { - if ((x[i] | x[i + 1] | x[i + 2] | x[i + 3] | x[i + 4] | x[i + 5] | x[i + 6] | x[i + 7]) != 0) + int once = Math.Min(zeros.Length, x.Length); + if (!zeros[..once].SequenceEqual(x[..once])) + { return true; - } + } - for (; i < x.Length; i++) - { - if (x[i] != 0) return true; + x = x[once..]; } return false; diff --git a/tests/Neo.VM.Tests/UT_Utility.cs b/tests/Neo.VM.Tests/UT_Utility.cs index 758c6ac100..30dd47101f 100644 --- a/tests/Neo.VM.Tests/UT_Utility.cs +++ b/tests/Neo.VM.Tests/UT_Utility.cs @@ -117,6 +117,20 @@ public void TestNotZero() Assert.IsTrue(new ReadOnlySpan(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }).NotZero()); Assert.IsTrue(new ReadOnlySpan(new byte[9] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 }).NotZero()); Assert.IsTrue(new ReadOnlySpan(new byte[11] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }).NotZero()); + + var bytes = new byte[64]; + for (int i = 0; i < bytes.Length; i++) + { + ReadOnlySpan span = bytes.AsSpan(); + Assert.IsFalse(span[i..].NotZero()); + + for (int j = i; j < bytes.Length; j++) + { + bytes[j] = 0x01; + Assert.IsTrue(span[i..].NotZero()); + bytes[j] = 0x00; + } + } } } } From a4ef07d9de69a13adc8f192917a996e0b0beb38e Mon Sep 17 00:00:00 2001 From: nan01ab Date: Fri, 20 Sep 2024 01:39:52 +0800 Subject: [PATCH 4/7] use ContainsAnyExcept to get better performace and simply code --- src/Neo.VM/Utility.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/Neo.VM/Utility.cs b/src/Neo.VM/Utility.cs index b1695a708a..d2a542a5ae 100644 --- a/src/Neo.VM/Utility.cs +++ b/src/Neo.VM/Utility.cs @@ -20,11 +20,6 @@ public static class Utility { public static Encoding StrictUTF8 { get; } - /// - /// A zero byte array. Length is 32 because it compares to short bytes now. - /// - private static readonly byte[] Zeros = new byte[32]; - static Utility() { StrictUTF8 = (Encoding)Encoding.UTF8.Clone(); @@ -40,19 +35,7 @@ static Utility() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool NotZero(this ReadOnlySpan x) { - var zeros = Zeros.AsSpan(); - for (; x.Length > 0;) - { - int once = Math.Min(zeros.Length, x.Length); - if (!zeros[..once].SequenceEqual(x[..once])) - { - return true; - } - - x = x[once..]; - } - - return false; + return x.ContainsAnyExcept((byte)0); } public static bool TryGetString(this ReadOnlySpan bytes, out string? value) From 6315b2823f76900788106dc48616d02dce6c53bb Mon Sep 17 00:00:00 2001 From: nan01ab Date: Fri, 20 Sep 2024 19:35:10 +0800 Subject: [PATCH 5/7] use ContainsAnyExcept to get better performance and simply code --- tests/Neo.VM.Tests/UT_Utility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Neo.VM.Tests/UT_Utility.cs b/tests/Neo.VM.Tests/UT_Utility.cs index 30dd47101f..8c2d4d2b4a 100644 --- a/tests/Neo.VM.Tests/UT_Utility.cs +++ b/tests/Neo.VM.Tests/UT_Utility.cs @@ -123,7 +123,7 @@ public void TestNotZero() { ReadOnlySpan span = bytes.AsSpan(); Assert.IsFalse(span[i..].NotZero()); - + for (int j = i; j < bytes.Length; j++) { bytes[j] = 0x01; From cf362b7d017f115b4b632eacab220e0ae3e4ca56 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Fri, 20 Sep 2024 23:44:32 +0800 Subject: [PATCH 6/7] use ContainsAnyExcept to get better performance and simply code --- src/Neo.VM/Utility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo.VM/Utility.cs b/src/Neo.VM/Utility.cs index d2a542a5ae..78c68ac4fa 100644 --- a/src/Neo.VM/Utility.cs +++ b/src/Neo.VM/Utility.cs @@ -35,7 +35,7 @@ static Utility() [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool NotZero(this ReadOnlySpan x) { - return x.ContainsAnyExcept((byte)0); + return x.IndexOfAnyExcept((byte)0) >= 0; } public static bool TryGetString(this ReadOnlySpan bytes, out string? value) From d3fc7d2d0221e4254251da9d178697274555341b Mon Sep 17 00:00:00 2001 From: nan01ab Date: Fri, 20 Sep 2024 23:44:53 +0800 Subject: [PATCH 7/7] fix comment --- src/Neo.VM/Utility.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Neo.VM/Utility.cs b/src/Neo.VM/Utility.cs index 78c68ac4fa..0821d77f08 100644 --- a/src/Neo.VM/Utility.cs +++ b/src/Neo.VM/Utility.cs @@ -31,7 +31,7 @@ static Utility() /// All bytes are zero or not in a byte array /// /// The byte array - /// true if all bytes are zero, otherwise it returns false + /// false if all bytes are zero, true otherwise [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool NotZero(this ReadOnlySpan x) {