From 8db80d459b922f90018ff607bb4a876df4cfd404 Mon Sep 17 00:00:00 2001 From: Erik Zhang Date: Mon, 1 Apr 2019 16:50:15 +0800 Subject: [PATCH] Use ReadOnlySpan and ReadOnlyMemory for reducing memory copy (#104) --- src/neo-vm/ExecutionEngine.cs | 147 +++++++++--------- src/neo-vm/HashComparer.cs | 2 +- src/neo-vm/Helper.cs | 14 ++ src/neo-vm/ICrypto.cs | 10 +- src/neo-vm/Instruction.cs | 85 ++++------ src/neo-vm/StackItem.cs | 28 +++- src/neo-vm/Types/Array.cs | 2 +- src/neo-vm/Types/Boolean.cs | 10 +- src/neo-vm/Types/ByteArray.cs | 12 +- src/neo-vm/Types/Integer.cs | 15 +- src/neo-vm/Types/InteropInterface.cs | 2 +- src/neo-vm/Types/Map.cs | 2 +- src/neo-vm/Unsafe.cs | 66 ++++---- src/neo-vm/neo-vm.csproj | 8 +- .../Extensions/ByteArrayExtensions.cs | 16 +- .../Extensions/StringExtensions.cs | 7 +- tests/neo-vm.Tests/Types/Crypto.cs | 38 ++--- tests/neo-vm.Tests/VMJsonTestBase.cs | 4 +- 18 files changed, 243 insertions(+), 225 deletions(-) create mode 100644 src/neo-vm/Helper.cs diff --git a/src/neo-vm/ExecutionEngine.cs b/src/neo-vm/ExecutionEngine.cs index f529184a..b13c7221 100644 --- a/src/neo-vm/ExecutionEngine.cs +++ b/src/neo-vm/ExecutionEngine.cs @@ -51,8 +51,6 @@ public class ExecutionEngine : IDisposable #endregion - private static readonly byte[] EmptyBytes = new byte[0]; - private int stackitem_count = 0; private bool is_stackitem_count_strict = true; @@ -108,7 +106,7 @@ public ExecutionEngine(IScriptContainer container, ICrypto crypto, IScriptTable /// Value /// Return True if are allowed, otherwise False [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CheckBigInteger(BigInteger value) => value.ToByteArray().Length <= MaxSizeForBigInteger; + public bool CheckBigInteger(BigInteger value) => value.GetByteCount() <= MaxSizeForBigInteger; /// /// Check if the number is allowed from SHL and SHR @@ -232,7 +230,7 @@ private bool ExecuteInstruction() // Push value case OpCode.PUSH0: { - context.EvaluationStack.Push(EmptyBytes); + context.EvaluationStack.Push(ReadOnlyMemory.Empty); if (!CheckStackSize(true)) return false; break; } @@ -321,10 +319,10 @@ private bool ExecuteInstruction() { if (table == null || (instruction.OpCode == OpCode.APPCALL && !CheckMaxInvocationStack())) return false; - byte[] script_hash = instruction.Operand; + byte[] script_hash = instruction.Operand.ToArray(); if (!Unsafe.NotZero(script_hash)) { - script_hash = context.EvaluationStack.Pop().GetByteArray(); + script_hash = context.EvaluationStack.Pop().GetByteArray().ToArray(); } ExecutionContext context_new = LoadScriptByHash(script_hash); if (context_new == null) return false; @@ -339,7 +337,7 @@ private bool ExecuteInstruction() case OpCode.SYSCALL: { if (instruction.Operand.Length > 252) return false; - if (Service?.Invoke(instruction.Operand, this) != true || !CheckStackSize(false, int.MaxValue)) + if (Service?.Invoke(instruction.Operand.ToArray(), this) != true || !CheckStackSize(false, int.MaxValue)) return false; break; } @@ -451,14 +449,27 @@ private bool ExecuteInstruction() } case OpCode.CAT: { - byte[] x2 = context.EvaluationStack.Pop().GetByteArray(); - byte[] x1 = context.EvaluationStack.Pop().GetByteArray(); - int length = x1.Length + x2.Length; - if (!CheckMaxItemSize(length)) return false; - byte[] buffer = new byte[length]; - Unsafe.MemoryCopy(x1, 0, buffer, 0, x1.Length); - Unsafe.MemoryCopy(x2, 0, buffer, x1.Length, x2.Length); - context.EvaluationStack.Push(buffer); + ReadOnlyMemory x2 = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory x1 = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory result; + if (x1.IsEmpty) + { + result = x2; + } + else if (x2.IsEmpty) + { + result = x1; + } + else + { + int length = x1.Length + x2.Length; + if (!CheckMaxItemSize(length)) return false; + Memory buffer = new byte[length]; + x1.CopyTo(buffer); + x2.CopyTo(buffer.Slice(x1.Length)); + result = buffer; + } + context.EvaluationStack.Push(result); CheckStackSize(true, -1); break; } @@ -468,12 +479,10 @@ private bool ExecuteInstruction() if (count < 0) return false; int index = (int)context.EvaluationStack.Pop().GetBigInteger(); if (index < 0) return false; - byte[] x = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory x = context.EvaluationStack.Pop().GetByteArray(); if (index > x.Length) return false; if (index + count > x.Length) count = x.Length - index; - byte[] buffer = new byte[count]; - Unsafe.MemoryCopy(x, index, buffer, 0, count); - context.EvaluationStack.Push(buffer); + context.EvaluationStack.Push(x.Slice(index, count)); CheckStackSize(true, -2); break; } @@ -481,18 +490,9 @@ private bool ExecuteInstruction() { int count = (int)context.EvaluationStack.Pop().GetBigInteger(); if (count < 0) return false; - byte[] x = context.EvaluationStack.Pop().GetByteArray(); - byte[] buffer; - if (count >= x.Length) - { - buffer = x; - } - else - { - buffer = new byte[count]; - Unsafe.MemoryCopy(x, 0, buffer, 0, count); - } - context.EvaluationStack.Push(buffer); + ReadOnlyMemory x = context.EvaluationStack.Pop().GetByteArray(); + if (count < x.Length) x = x.Slice(0, count); + context.EvaluationStack.Push(x); CheckStackSize(true, -1); break; } @@ -500,19 +500,10 @@ private bool ExecuteInstruction() { int count = (int)context.EvaluationStack.Pop().GetBigInteger(); if (count < 0) return false; - byte[] x = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory x = context.EvaluationStack.Pop().GetByteArray(); if (count > x.Length) return false; - byte[] buffer; - if (count == x.Length) - { - buffer = x; - } - else - { - buffer = new byte[count]; - Unsafe.MemoryCopy(x, x.Length - count, buffer, 0, count); - } - context.EvaluationStack.Push(buffer); + if (count < x.Length) x = x.Slice(x.Length - count); + context.EvaluationStack.Push(x); CheckStackSize(true, -1); break; } @@ -817,37 +808,49 @@ private bool ExecuteInstruction() case OpCode.SHA1: using (SHA1 sha = SHA1.Create()) { - byte[] x = context.EvaluationStack.Pop().GetByteArray(); - context.EvaluationStack.Push(sha.ComputeHash(x)); + ReadOnlyMemory x = context.EvaluationStack.Pop().GetByteArray(); +#if NETCOREAPP + byte[] hash = new byte[sha.HashSize / 8]; + sha.TryComputeHash(x.Span, hash, out _); +#else + byte[] hash = sha.ComputeHash(x.ToArray()); +#endif + context.EvaluationStack.Push(hash); break; } case OpCode.SHA256: using (SHA256 sha = SHA256.Create()) { - byte[] x = context.EvaluationStack.Pop().GetByteArray(); - context.EvaluationStack.Push(sha.ComputeHash(x)); + ReadOnlyMemory x = context.EvaluationStack.Pop().GetByteArray(); +#if NETCOREAPP + byte[] hash = new byte[sha.HashSize / 8]; + sha.TryComputeHash(x.Span, hash, out _); +#else + byte[] hash = sha.ComputeHash(x.ToArray()); +#endif + context.EvaluationStack.Push(hash); break; } case OpCode.HASH160: { - byte[] x = context.EvaluationStack.Pop().GetByteArray(); - context.EvaluationStack.Push(Crypto.Hash160(x)); + ReadOnlyMemory x = context.EvaluationStack.Pop().GetByteArray(); + context.EvaluationStack.Push(Crypto.Hash160(x.Span)); break; } case OpCode.HASH256: { - byte[] x = context.EvaluationStack.Pop().GetByteArray(); - context.EvaluationStack.Push(Crypto.Hash256(x)); + ReadOnlyMemory x = context.EvaluationStack.Pop().GetByteArray(); + context.EvaluationStack.Push(Crypto.Hash256(x.Span)); break; } case OpCode.CHECKSIG: { - byte[] pubkey = context.EvaluationStack.Pop().GetByteArray(); - byte[] signature = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory pubkey = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory signature = context.EvaluationStack.Pop().GetByteArray(); try { - context.EvaluationStack.Push(Crypto.VerifySignature(ScriptContainer.GetMessage(), signature, pubkey)); + context.EvaluationStack.Push(Crypto.VerifySignature(ScriptContainer.GetMessage(), signature.Span, pubkey.Span)); } catch (ArgumentException) { @@ -858,13 +861,13 @@ private bool ExecuteInstruction() } case OpCode.VERIFY: { - byte[] pubkey = context.EvaluationStack.Pop().GetByteArray(); - byte[] signature = context.EvaluationStack.Pop().GetByteArray(); - byte[] message = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory pubkey = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory signature = context.EvaluationStack.Pop().GetByteArray(); + ReadOnlyMemory message = context.EvaluationStack.Pop().GetByteArray(); try { - context.EvaluationStack.Push(Crypto.VerifySignature(message, signature, pubkey)); + context.EvaluationStack.Push(Crypto.VerifySignature(message.Span, signature.Span, pubkey.Span)); } catch (ArgumentException) { @@ -876,7 +879,7 @@ private bool ExecuteInstruction() case OpCode.CHECKMULTISIG: { int n; - byte[][] pubkeys; + ReadOnlyMemory[] pubkeys; StackItem item = context.EvaluationStack.Pop(); if (item is VMArray array1) @@ -890,14 +893,14 @@ private bool ExecuteInstruction() { n = (int)item.GetBigInteger(); if (n < 1 || n > context.EvaluationStack.Count) return false; - pubkeys = new byte[n][]; + pubkeys = new ReadOnlyMemory[n]; for (int i = 0; i < n; i++) pubkeys[i] = context.EvaluationStack.Pop().GetByteArray(); CheckStackSize(true, -n - 1); } int m; - byte[][] signatures; + ReadOnlyMemory[] signatures; item = context.EvaluationStack.Pop(); if (item is VMArray array2) { @@ -910,7 +913,7 @@ private bool ExecuteInstruction() { m = (int)item.GetBigInteger(); if (m < 1 || m > n || m > context.EvaluationStack.Count) return false; - signatures = new byte[m][]; + signatures = new ReadOnlyMemory[m]; for (int i = 0; i < m; i++) signatures[i] = context.EvaluationStack.Pop().GetByteArray(); CheckStackSize(true, -m - 1); @@ -921,7 +924,7 @@ private bool ExecuteInstruction() { for (int i = 0, j = 0; fSuccess && i < m && j < n;) { - if (Crypto.VerifySignature(message, signatures[i], pubkeys[j])) + if (Crypto.VerifySignature(message, signatures[i].Span, pubkeys[j].Span)) i++; j++; if (m - i > n - j) @@ -997,10 +1000,10 @@ private bool ExecuteInstruction() } default: { - byte[] byteArray = item.GetByteArray(); + ReadOnlyMemory byteArray = item.GetByteArray(); int index = (int)key.GetBigInteger(); if (index < 0 || index >= byteArray.Length) return false; - context.EvaluationStack.Push((int)byteArray[index]); + context.EvaluationStack.Push((int)byteArray.Span[index]); CheckStackSize(false, -1); break; } @@ -1192,8 +1195,8 @@ private bool ExecuteInstruction() case OpCode.CALL_I: { if (!CheckMaxInvocationStack()) return false; - int rvcount = instruction.Operand[0]; - int pcount = instruction.Operand[1]; + int rvcount = instruction.Operand.Span[0]; + int pcount = instruction.Operand.Span[1]; if (context.EvaluationStack.Count < pcount) return false; ExecutionContext context_call = LoadScript(context.Script, rvcount); context_call.InstructionPointer = context.InstructionPointer + instruction.TokenI16_1; @@ -1209,8 +1212,8 @@ private bool ExecuteInstruction() case OpCode.CALL_EDT: { if (table == null) return false; - int rvcount = instruction.Operand[0]; - int pcount = instruction.Operand[1]; + int rvcount = instruction.Operand.Span[0]; + int pcount = instruction.Operand.Span[1]; if (context.EvaluationStack.Count < pcount) return false; if (instruction.OpCode == OpCode.CALL_ET || instruction.OpCode == OpCode.CALL_EDT) { @@ -1224,12 +1227,12 @@ private bool ExecuteInstruction() byte[] script_hash; if (instruction.OpCode == OpCode.CALL_ED || instruction.OpCode == OpCode.CALL_EDT) { - script_hash = context.EvaluationStack.Pop().GetByteArray(); + script_hash = context.EvaluationStack.Pop().GetByteArray().ToArray(); CheckStackSize(true, -1); } else { - script_hash = instruction.ReadBytes(2, 20); + script_hash = instruction.Operand.Slice(2, 20).ToArray(); } ExecutionContext context_new = LoadScriptByHash(script_hash, rvcount); @@ -1277,7 +1280,7 @@ protected virtual ExecutionContext LoadScript(Script script, int rvcount = -1) private ExecutionContext LoadScriptByHash(byte[] hash, int rvcount = -1) { if (table == null) return null; - byte[] script = table.GetScript(hash); + byte[] script = table.GetScript(hash.ToArray()); if (script == null) return null; return LoadScript(new Script(hash, script), rvcount); } diff --git a/src/neo-vm/HashComparer.cs b/src/neo-vm/HashComparer.cs index aed8b118..2de2f113 100644 --- a/src/neo-vm/HashComparer.cs +++ b/src/neo-vm/HashComparer.cs @@ -6,7 +6,7 @@ internal class HashComparer : IEqualityComparer { public bool Equals(byte[] x, byte[] y) { - return Unsafe.Equals(x, y); + return Unsafe.SpanEquals(x, y); } public int GetHashCode(byte[] obj) diff --git a/src/neo-vm/Helper.cs b/src/neo-vm/Helper.cs new file mode 100644 index 00000000..9e60b7f8 --- /dev/null +++ b/src/neo-vm/Helper.cs @@ -0,0 +1,14 @@ +#if !NETCOREAPP +using System.Numerics; + +namespace Neo.VM +{ + internal static class Helper + { + public static int GetByteCount(this BigInteger source) + { + return source.ToByteArray().Length; + } + } +} +#endif diff --git a/src/neo-vm/ICrypto.cs b/src/neo-vm/ICrypto.cs index 11f55c2f..19493abc 100644 --- a/src/neo-vm/ICrypto.cs +++ b/src/neo-vm/ICrypto.cs @@ -1,11 +1,13 @@ -namespace Neo.VM +using System; + +namespace Neo.VM { public interface ICrypto { - byte[] Hash160(byte[] message); + byte[] Hash160(ReadOnlySpan message); - byte[] Hash256(byte[] message); + byte[] Hash256(ReadOnlySpan message); - bool VerifySignature(byte[] message, byte[] signature, byte[] pubkey); + bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ReadOnlySpan pubkey); } } diff --git a/src/neo-vm/Instruction.cs b/src/neo-vm/Instruction.cs index cb118954..61e34d49 100644 --- a/src/neo-vm/Instruction.cs +++ b/src/neo-vm/Instruction.cs @@ -8,7 +8,7 @@ public class Instruction public static Instruction RET { get; } = new Instruction(OpCode.RET); public readonly OpCode OpCode; - public readonly byte[] Operand; + public readonly ReadOnlyMemory Operand; private static readonly int[] OperandSizePrefixTable = new int[256]; private static readonly int[] OperandSizeTable = new int[256]; @@ -29,7 +29,19 @@ public short TokenI16 { get { - return BitConverter.ToInt16(Operand, 0); +#if NETCOREAPP + return BitConverter.ToInt16(Operand.Span); +#else + if (Operand.Length < sizeof(short)) + throw new InvalidOperationException(); + unsafe + { + fixed (byte* pbyte = Operand.Span) + { + return *(short*)pbyte; + } + } +#endif } } @@ -37,7 +49,19 @@ public short TokenI16_1 { get { - return BitConverter.ToInt16(Operand, sizeof(short)); +#if NETCOREAPP + return BitConverter.ToInt16(Operand.Span.Slice(2, 2)); +#else + if (Operand.Length < sizeof(short) * 2) + throw new InvalidOperationException(); + unsafe + { + fixed (byte* pbyte = &Operand.Span[sizeof(short)]) + { + return *(short*)pbyte; + } + } +#endif } } @@ -70,68 +94,25 @@ private Instruction(OpCode opcode) internal Instruction(byte[] script, int ip) { this.OpCode = (OpCode)script[ip++]; + int operandSizePrefix = OperandSizePrefixTable[(int)OpCode]; int operandSize = 0; - switch (OperandSizePrefixTable[(int)OpCode]) + switch (operandSizePrefix) { case 0: operandSize = OperandSizeTable[(int)OpCode]; break; case 1: - operandSize = ReadByte(script, ref ip); + operandSize = script[ip]; break; case 2: - operandSize = ReadUInt16(script, ref ip); + operandSize = BitConverter.ToUInt16(script, ip); break; case 4: - operandSize = ReadInt32(script, ref ip); + operandSize = BitConverter.ToInt32(script, ip); break; } if (operandSize > 0) - this.Operand = ReadBytes(script, ref ip, operandSize); - } - - private static byte ReadByte(byte[] script, ref int ip) - { - if (ip + sizeof(byte) > script.Length) - throw new InvalidOperationException(); - return script[ip++]; - } - - private static byte[] ReadBytes(byte[] script, ref int ip, int count) - { - if (ip + count > script.Length) - throw new InvalidOperationException(); - byte[] buffer = new byte[count]; - Unsafe.MemoryCopy(script, ip, buffer, 0, count); - ip += count; - return buffer; - } - - public byte[] ReadBytes(int offset, int count) - { - if (offset + count > Operand.Length) - throw new InvalidOperationException(); - byte[] buffer = new byte[count]; - Unsafe.MemoryCopy(Operand, offset, buffer, 0, count); - return buffer; - } - - private static int ReadInt32(byte[] script, ref int ip) - { - if (ip + sizeof(int) > script.Length) - throw new InvalidOperationException(); - int value = Unsafe.ToInt32(script, ip); - ip += sizeof(int); - return value; - } - - private static ushort ReadUInt16(byte[] script, ref int ip) - { - if (ip + sizeof(ushort) > script.Length) - throw new InvalidOperationException(); - ushort value = Unsafe.ToUInt16(script, ip); - ip += sizeof(ushort); - return value; + this.Operand = new ReadOnlyMemory(script, ip + operandSizePrefix, operandSize); } } } diff --git a/src/neo-vm/StackItem.cs b/src/neo-vm/StackItem.cs index 17e28adb..97f5f052 100644 --- a/src/neo-vm/StackItem.cs +++ b/src/neo-vm/StackItem.cs @@ -29,12 +29,16 @@ public static StackItem FromInterface(T value) public virtual BigInteger GetBigInteger() { - return new BigInteger(GetByteArray()); +#if NETCOREAPP + return new BigInteger(GetByteArray().Span); +#else + return new BigInteger(GetByteArray().ToArray()); +#endif } public abstract bool GetBoolean(); - public abstract byte[] GetByteArray(); + public abstract ReadOnlyMemory GetByteArray(); public virtual int GetByteLength() { @@ -46,7 +50,7 @@ public override int GetHashCode() unchecked { int hash = 17; - foreach (byte element in GetByteArray()) + foreach (byte element in GetByteArray().Span) hash = hash * 31 + element; return hash; } @@ -54,7 +58,18 @@ public override int GetHashCode() public virtual string GetString() { - return Encoding.UTF8.GetString(GetByteArray()); +#if NETCOREAPP + return Encoding.UTF8.GetString(GetByteArray().Span); +#else + ReadOnlySpan span = GetByteArray().Span; + unsafe + { + fixed (byte* bp = span) + { + return Encoding.UTF8.GetString(bp, span.Length); + } + } +#endif } public static implicit operator StackItem(int value) @@ -92,6 +107,11 @@ public static implicit operator StackItem(byte[] value) return new ByteArray(value); } + public static implicit operator StackItem(ReadOnlyMemory value) + { + return new ByteArray(value); + } + public static implicit operator StackItem(string value) { return new ByteArray(Encoding.UTF8.GetBytes(value)); diff --git a/src/neo-vm/Types/Array.cs b/src/neo-vm/Types/Array.cs index 7f8a740f..7c6ff427 100644 --- a/src/neo-vm/Types/Array.cs +++ b/src/neo-vm/Types/Array.cs @@ -64,7 +64,7 @@ public override bool GetBoolean() return true; } - public override byte[] GetByteArray() + public override ReadOnlyMemory GetByteArray() { throw new NotSupportedException(); } diff --git a/src/neo-vm/Types/Boolean.cs b/src/neo-vm/Types/Boolean.cs index 8704634f..5288a2d8 100644 --- a/src/neo-vm/Types/Boolean.cs +++ b/src/neo-vm/Types/Boolean.cs @@ -5,8 +5,8 @@ namespace Neo.VM.Types { public class Boolean : StackItem { - private static readonly byte[] TRUE = { 1 }; - private static readonly byte[] FALSE = new byte[0]; + private static readonly ReadOnlyMemory TRUE = new byte[] { 1 }; + private static readonly ReadOnlyMemory FALSE = ReadOnlyMemory.Empty; private bool value; @@ -20,7 +20,7 @@ public override bool Equals(StackItem other) if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(null, other)) return false; if (other is Boolean b) return value == b.value; - byte[] bytes_other; + ReadOnlyMemory bytes_other; try { bytes_other = other.GetByteArray(); @@ -29,7 +29,7 @@ public override bool Equals(StackItem other) { return false; } - return Unsafe.Equals(GetByteArray(), bytes_other); + return Unsafe.SpanEquals(GetByteArray().Span, bytes_other.Span); } public override BigInteger GetBigInteger() @@ -42,7 +42,7 @@ public override bool GetBoolean() return value; } - public override byte[] GetByteArray() + public override ReadOnlyMemory GetByteArray() { return value ? TRUE : FALSE; } diff --git a/src/neo-vm/Types/ByteArray.cs b/src/neo-vm/Types/ByteArray.cs index 15eeaa56..0ccc74ef 100644 --- a/src/neo-vm/Types/ByteArray.cs +++ b/src/neo-vm/Types/ByteArray.cs @@ -4,9 +4,9 @@ namespace Neo.VM.Types { public class ByteArray : StackItem { - private byte[] value; + private ReadOnlyMemory value; - public ByteArray(byte[] value) + public ByteArray(ReadOnlyMemory value) { this.value = value; } @@ -15,7 +15,7 @@ public override bool Equals(StackItem other) { if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(null, other)) return false; - byte[] bytes_other; + ReadOnlyMemory bytes_other; try { bytes_other = other.GetByteArray(); @@ -24,17 +24,17 @@ public override bool Equals(StackItem other) { return false; } - return Unsafe.Equals(value, bytes_other); + return Unsafe.SpanEquals(value.Span, bytes_other.Span); } public override bool GetBoolean() { if (value.Length > ExecutionEngine.MaxSizeForBigInteger) return true; - return Unsafe.NotZero(value); + return Unsafe.NotZero(value.Span); } - public override byte[] GetByteArray() + public override ReadOnlyMemory GetByteArray() { return value; } diff --git a/src/neo-vm/Types/Integer.cs b/src/neo-vm/Types/Integer.cs index 7933a93e..36dd86aa 100644 --- a/src/neo-vm/Types/Integer.cs +++ b/src/neo-vm/Types/Integer.cs @@ -17,7 +17,7 @@ public override bool Equals(StackItem other) if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(null, other)) return false; if (other is Integer i) return value == i.value; - byte[] bytes_other; + ReadOnlyMemory bytes_other; try { bytes_other = other.GetByteArray(); @@ -26,7 +26,7 @@ public override bool Equals(StackItem other) { return false; } - return Unsafe.Equals(GetByteArray(), bytes_other); + return Unsafe.SpanEquals(GetByteArray().Span, bytes_other.Span); } public override BigInteger GetBigInteger() @@ -39,17 +39,24 @@ public override bool GetBoolean() return !value.IsZero; } - public override byte[] GetByteArray() + public override ReadOnlyMemory GetByteArray() { return value.ToByteArray(); } +#if NETCOREAPP + public override int GetByteLength() + { + return value.GetByteCount(); + } +#else private int _length = -1; public override int GetByteLength() { if (_length == -1) - _length = value.ToByteArray().Length; + _length = value.GetByteCount(); return _length; } +#endif } } diff --git a/src/neo-vm/Types/InteropInterface.cs b/src/neo-vm/Types/InteropInterface.cs index 7e343cdb..72880674 100644 --- a/src/neo-vm/Types/InteropInterface.cs +++ b/src/neo-vm/Types/InteropInterface.cs @@ -4,7 +4,7 @@ namespace Neo.VM.Types { public abstract class InteropInterface : StackItem { - public override byte[] GetByteArray() + public override ReadOnlyMemory GetByteArray() { throw new NotSupportedException(); } diff --git a/src/neo-vm/Types/Map.cs b/src/neo-vm/Types/Map.cs index c053a622..2688ee10 100644 --- a/src/neo-vm/Types/Map.cs +++ b/src/neo-vm/Types/Map.cs @@ -76,7 +76,7 @@ public override bool GetBoolean() return true; } - public override byte[] GetByteArray() + public override ReadOnlyMemory GetByteArray() { throw new NotSupportedException(); } diff --git a/src/neo-vm/Unsafe.cs b/src/neo-vm/Unsafe.cs index 6a705216..cf5f7d0b 100644 --- a/src/neo-vm/Unsafe.cs +++ b/src/neo-vm/Unsafe.cs @@ -5,33 +5,6 @@ namespace Neo.VM { internal static class Unsafe { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - unsafe public static bool Equals(byte[] x, byte[] y) - { - if (ReferenceEquals(x, y)) return true; - if (x is null || y is null) return false; - int len = x.Length; - if (len != y.Length) return false; - fixed (byte* xp = x, yp = y) - { - long* xlp = (long*)xp, ylp = (long*)yp; - for (; len >= 8; len -= 8) - { - if (*xlp != *ylp) return false; - xlp++; - ylp++; - } - byte* xbp = (byte*)xlp, ybp = (byte*)ylp; - for (; len > 0; len--) - { - if (*xbp != *ybp) return false; - xbp++; - ybp++; - } - } - return true; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] unsafe public static void MemoryCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count) { @@ -43,10 +16,8 @@ unsafe public static void MemoryCopy(byte[] src, int srcOffset, byte[] dst, int } [MethodImpl(MethodImplOptions.AggressiveInlining)] - unsafe public static bool NotZero(byte[] x) + unsafe public static bool NotZero(ReadOnlySpan x) { - if (x is null) - throw new ArgumentNullException(nameof(x)); int len = x.Length; if (len == 0) return false; fixed (byte* xp = x) @@ -67,6 +38,32 @@ unsafe public static bool NotZero(byte[] x) return false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + unsafe public static bool SpanEquals(ReadOnlySpan x, ReadOnlySpan y) + { + if (x == y) return true; + int len = x.Length; + if (len != y.Length) return false; + fixed (byte* xp = x, yp = y) + { + long* xlp = (long*)xp, ylp = (long*)yp; + for (; len >= 8; len -= 8) + { + if (*xlp != *ylp) return false; + xlp++; + ylp++; + } + byte* xbp = (byte*)xlp, ybp = (byte*)ylp; + for (; len > 0; len--) + { + if (*xbp != *ybp) return false; + xbp++; + ybp++; + } + } + return true; + } + /// /// Convert byte array to int32 /// @@ -81,14 +78,5 @@ unsafe public static int ToInt32(byte[] value, int startIndex) return *(int*)pbyte; } } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - unsafe public static ushort ToUInt16(byte[] value, int startIndex) - { - fixed (byte* pbyte = &value[startIndex]) - { - return *(ushort*)pbyte; - } - } } } diff --git a/src/neo-vm/neo-vm.csproj b/src/neo-vm/neo-vm.csproj index 3d7ef1b7..ba692e06 100644 --- a/src/neo-vm/neo-vm.csproj +++ b/src/neo-vm/neo-vm.csproj @@ -6,16 +6,16 @@ Neo.VM 2.4.0 The Neo Project - netstandard1.6;net461 + netstandard1.6;netcoreapp2.1;net461 Neo.VM Neo.VM NEO;AntShares;Blockchain;Smart Contract;VM https://github.com/neo-project/neo-vm git https://github.com/neo-project/neo-vm.git - 1.6.0 Neo.VM true + latest @@ -24,4 +24,8 @@ + + + + diff --git a/tests/neo-vm.Tests/Extensions/ByteArrayExtensions.cs b/tests/neo-vm.Tests/Extensions/ByteArrayExtensions.cs index f9a3a795..7aabdb1a 100644 --- a/tests/neo-vm.Tests/Extensions/ByteArrayExtensions.cs +++ b/tests/neo-vm.Tests/Extensions/ByteArrayExtensions.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; -using System.Linq; +using Neo.Test.Cryptography; +using System; using System.Security.Cryptography; using System.Threading; -using Neo.Test.Cryptography; namespace Neo.Test.Extensions { @@ -11,19 +10,14 @@ public static class ByteArrayExtensions private static ThreadLocal _sha256 = new ThreadLocal(() => SHA256.Create()); private static ThreadLocal _ripemd160 = new ThreadLocal(() => new RIPEMD160Managed()); - public static byte[] RIPEMD160(this IEnumerable value) + public static ReadOnlySpan RIPEMD160(this ReadOnlySpan value) { return _ripemd160.Value.ComputeHash(value.ToArray()); } - public static byte[] Sha256(this IEnumerable value) + public static ReadOnlySpan Sha256(this ReadOnlySpan value) { return _sha256.Value.ComputeHash(value.ToArray()); } - - public static byte[] Sha256(this byte[] value, int offset, int count) - { - return _sha256.Value.ComputeHash(value, offset, count); - } } -} \ No newline at end of file +} diff --git a/tests/neo-vm.Tests/Extensions/StringExtensions.cs b/tests/neo-vm.Tests/Extensions/StringExtensions.cs index 972e7bb2..b9feb6e2 100644 --- a/tests/neo-vm.Tests/Extensions/StringExtensions.cs +++ b/tests/neo-vm.Tests/Extensions/StringExtensions.cs @@ -12,7 +12,12 @@ internal static class StringExtensions /// Return hex string public static string ToHexString(this byte[] data) { - if (data == null) return ""; + return ToHexString((ReadOnlySpan)data); + } + + public static string ToHexString(this ReadOnlySpan data) + { + if (data.IsEmpty) return ""; var m = data.Length; if (m == 0) return ""; diff --git a/tests/neo-vm.Tests/Types/Crypto.cs b/tests/neo-vm.Tests/Types/Crypto.cs index 980fd22d..fef30446 100644 --- a/tests/neo-vm.Tests/Types/Crypto.cs +++ b/tests/neo-vm.Tests/Types/Crypto.cs @@ -1,8 +1,7 @@ -using System; -using System.Linq; -using System.Security.Cryptography; -using Neo.Test.Extensions; +using Neo.Test.Extensions; using Neo.VM; +using System; +using System.Security.Cryptography; namespace Neo.Test.Types { @@ -10,40 +9,41 @@ public class Crypto : ICrypto { public static readonly Crypto Default = new Crypto(); - public byte[] Hash160(byte[] message) + public byte[] Hash160(ReadOnlySpan message) { - return message.Sha256().RIPEMD160(); + return message.Sha256().RIPEMD160().ToArray(); } - public byte[] Hash256(byte[] message) + public byte[] Hash256(ReadOnlySpan message) { - return message.Sha256().Sha256(); + return message.Sha256().Sha256().ToArray(); } - public byte[] Sign(byte[] message, byte[] prikey, byte[] pubkey) + public byte[] Sign(ReadOnlySpan message, ReadOnlySpan prikey, ReadOnlySpan pubkey) { using (var ecdsa = ECDsa.Create(new ECParameters { Curve = ECCurve.NamedCurves.nistP256, - D = prikey, + D = prikey.ToArray(), Q = new ECPoint { - X = pubkey.Take(32).ToArray(), - Y = pubkey.Skip(32).ToArray() + X = pubkey.Slice(0, 32).ToArray(), + Y = pubkey.Slice(32).ToArray() } })) { - return ecdsa.SignData(message, HashAlgorithmName.SHA256); + return ecdsa.SignData(message.ToArray(), HashAlgorithmName.SHA256); } } - public bool VerifySignature(byte[] message, byte[] signature, byte[] pubkey) + public bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ReadOnlySpan pubkey) { if (pubkey.Length == 33 && (pubkey[0] == 0x02 || pubkey[0] == 0x03)) { try { - pubkey = Neo.Cryptography.ECC.ECPoint.DecodePoint(pubkey, Neo.Cryptography.ECC.ECCurve.Secp256r1).EncodePoint(false).Skip(1).ToArray(); + pubkey = Neo.Cryptography.ECC.ECPoint.DecodePoint(pubkey.ToArray(), Neo.Cryptography.ECC.ECCurve.Secp256r1).EncodePoint(false); + pubkey = pubkey.Slice(1); } catch { @@ -52,7 +52,7 @@ public bool VerifySignature(byte[] message, byte[] signature, byte[] pubkey) } else if (pubkey.Length == 65 && pubkey[0] == 0x04) { - pubkey = pubkey.Skip(1).ToArray(); + pubkey = pubkey.Slice(1); } else if (pubkey.Length != 64) { @@ -63,8 +63,8 @@ public bool VerifySignature(byte[] message, byte[] signature, byte[] pubkey) Curve = ECCurve.NamedCurves.nistP256, Q = new ECPoint { - X = pubkey.Take(32).ToArray(), - Y = pubkey.Skip(32).ToArray() + X = pubkey.Slice(0, 32).ToArray(), + Y = pubkey.Slice(32).ToArray() } })) { @@ -72,4 +72,4 @@ public bool VerifySignature(byte[] message, byte[] signature, byte[] pubkey) } } } -} \ No newline at end of file +} diff --git a/tests/neo-vm.Tests/VMJsonTestBase.cs b/tests/neo-vm.Tests/VMJsonTestBase.cs index de3fdcf4..3b2c1e3a 100644 --- a/tests/neo-vm.Tests/VMJsonTestBase.cs +++ b/tests/neo-vm.Tests/VMJsonTestBase.cs @@ -218,7 +218,7 @@ private JToken ItemToJson(StackItem item) { case VM.Types.Boolean v: value = new JValue(v.GetBoolean()); break; case VM.Types.Integer v: value = new JValue(v.GetBigInteger().ToString()); break; - case VM.Types.ByteArray v: value = new JValue(v.GetByteArray()); break; + case VM.Types.ByteArray v: value = new JValue(v.GetByteArray().ToArray()); break; //case VM.Types.Struct v: case VM.Types.Array v: { @@ -238,7 +238,7 @@ private JToken ItemToJson(StackItem item) foreach (var entry in v) { - jdic.Add(entry.Key.GetByteArray().ToHexString(), ItemToJson(entry.Value)); + jdic.Add(entry.Key.GetByteArray().Span.ToHexString(), ItemToJson(entry.Value)); } value = jdic;