From 64e13bbe85863328f5d3cc7e7cf479f4ad3d5997 Mon Sep 17 00:00:00 2001 From: nan01ab Date: Thu, 7 Nov 2024 19:16:00 +0800 Subject: [PATCH] `[Fix]` unalignment memory load in Neo.VM Unsafe.NotZero(ReadOnlySpan) and remove the use of `unsafe` (#3492) * fix: unalignment load in ReadOnlySpan.NotZero * Update .gitignore * use SequenceEqual to compare bytes for better performace * use ContainsAnyExcept to get better performace and simply code * use ContainsAnyExcept to get better performance and simply code * use ContainsAnyExcept to get better performance and simply code * fix comment * merge master and avoid unnecessary change * move notzero test * change to block namespace * Add attribute --------- Co-authored-by: Shargon Co-authored-by: Christopher Schuchardt Co-authored-by: Jimmy --- .gitignore | 4 +++- src/Neo.VM/Types/ByteString.cs | 2 +- src/Neo.VM/Unsafe.cs | 11 ++++++++++- tests/Neo.VM.Tests/UT_Unsafe.cs | 33 ++++++++++++++++++++++++++------- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 45ccb8c40a..16342efe06 100644 --- a/.gitignore +++ b/.gitignore @@ -257,6 +257,8 @@ paket-files/ PublishProfiles /.vscode launchSettings.json +/coverages +**/.DS_Store # Benchmarks -**/BenchmarkDotNet.Artifacts/ \ No newline at end of file +**/BenchmarkDotNet.Artifacts/ diff --git a/src/Neo.VM/Types/ByteString.cs b/src/Neo.VM/Types/ByteString.cs index 8869092b03..d2f1ebee65 100644 --- a/src/Neo.VM/Types/ByteString.cs +++ b/src/Neo.VM/Types/ByteString.cs @@ -80,7 +80,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 BigInteger GetInteger() diff --git a/src/Neo.VM/Unsafe.cs b/src/Neo.VM/Unsafe.cs index 86380722b6..fe4c974437 100644 --- a/src/Neo.VM/Unsafe.cs +++ b/src/Neo.VM/Unsafe.cs @@ -19,9 +19,17 @@ unsafe internal static class Unsafe { private const long DefaultXxHash3Seed = 40343; + /// + /// All bytes are zero or not in a byte array + /// + /// The byte array + /// false if all bytes are zero, true otherwise [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool NotZero(ReadOnlySpan x) + public static bool NotZero(this ReadOnlySpan x) { +#if NET7_0_OR_GREATER + return x.IndexOfAnyExcept((byte)0) >= 0; +#else int len = x.Length; if (len == 0) return false; fixed (byte* xp = x) @@ -40,6 +48,7 @@ public static bool NotZero(ReadOnlySpan x) } } return false; +#endif } /// diff --git a/tests/Neo.VM.Tests/UT_Unsafe.cs b/tests/Neo.VM.Tests/UT_Unsafe.cs index e3b4b8708f..f42903c3cc 100644 --- a/tests/Neo.VM.Tests/UT_Unsafe.cs +++ b/tests/Neo.VM.Tests/UT_Unsafe.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.VM; +using System; namespace Neo.Test { @@ -20,14 +21,32 @@ 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.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(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 })); + 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()); + + 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; + } + } } } }