Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IL Virtual Machine #3888

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
237 changes: 237 additions & 0 deletions src/Nethermind/Nethermind.Evm.Test/IlVirtualMachineTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Nethermind library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Nethermind. If not, see <http://www.gnu.org/licenses/>.
//

using System;
using System.Buffers.Binary;
using System.Diagnostics;
using Nethermind.Core.Test;
using Nethermind.Logging;
using NUnit.Framework;

namespace Nethermind.Evm.Test;

public class IlVirtualMachineTests : VirtualMachineTestsBase
{
protected override ILogManager GetLogManager() => new NUnitLogManager();

[Test]
public void PC_POP()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.Op(Instruction.PC)
.Op(Instruction.POP)
.Done;

TestAllTracerWithOutput result = Execute(code);

Console.WriteLine(result.Error);
}

[Test]
public void Jump_Invalid()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(1)
.Op(Instruction.JUMP)
.Done;

TestAllTracerWithOutput result = Execute(code);

Assert.AreEqual(result.Error, EvmExceptionType.InvalidJumpDestination.ToString());
}

[Test]
public void Jump_Valid()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(3)
.Op(Instruction.JUMP)
.Op(Instruction.JUMPDEST)
.Done;

TestAllTracerWithOutput result = Execute(code);

Console.WriteLine(result.Error);
}

[Test]
public void Jumpi_InvalidCondition()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(0) // invalid condition
.PushData(1) // address
.Op(Instruction.JUMPI)
.Done;

TestAllTracerWithOutput result = Execute(code);

Console.WriteLine(result.Error);
}

[Test]
public void Jumpi_ValidCondition_InvalidAddress()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(1) // valid condition
.PushData(1) // address
.Op(Instruction.JUMPI)
.Done;

TestAllTracerWithOutput result = Execute(code);

Assert.AreEqual(result.Error, EvmExceptionType.InvalidJumpDestination.ToString());
}

[Test]
public void Jumpi_ValidCondition_ValidAddress()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(1) // valid condition
.PushData(5) // address
.Op(Instruction.JUMPI)
.Op(Instruction.JUMPDEST)
.Done;

TestAllTracerWithOutput result = Execute(code);

Console.WriteLine(result.Error);
}

[Test]
public void Sub()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(1)
.PushData(2)
.PushData(1)
.PushData(4)
.Op(Instruction.SUB) // 4 - 1 = 3
.Op(Instruction.SUB) // 3 - 2 = 1
.Op(Instruction.SUB) // 1 - 1 = 0
.Done;

TestAllTracerWithOutput result = Execute(code);

Console.WriteLine(result.Error);
}

[Test]
public void Dup()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(1)
.Op(Instruction.DUP1)
.Op(Instruction.SUB)
.Op(Instruction.POP)
.Done;

TestAllTracerWithOutput result = Execute(code);

Console.WriteLine(result.Error);
}

[Test]
public void Swap()
{
Machine.BuildILForNext();

byte[] code = Prepare.EvmCode
.PushData(2)
.PushData(1)
.Op(Instruction.SWAP1)
.Op(Instruction.SUB)
.Done;

TestAllTracerWithOutput result = Execute(code);

Console.WriteLine(result.Error);
}

//[Test]
//public void OutOfGas()
//{
// // an infinite loop
// byte[] code = Prepare.EvmCode
// .Op(Instruction.JUMPDEST)
// .PushData(0)
// .Op(Instruction.JUMP)
// .Done;

// TestAllTracerWithOutput result = Execute(BlockNumber, 10000, code);

// Assert.AreEqual(result.Error, EvmExceptionType.OutOfGas.ToString());
//}

[TestCase(true, TestName = "IL")]
[TestCase(false, TestName = "Interpreter")]
[Explicit]
public void Long_Loop(bool isIL)
{
if (isIL)
{
Machine.BuildILForNext();
}

const int loopCount = 200_000;
byte[] repeat = new byte[4];
BinaryPrimitives.TryWriteInt32BigEndian(repeat, loopCount);

Prepare code = Prepare.EvmCode
.PushData(repeat)
.Op(Instruction.JUMPDEST) // counter
.PushData(1) // counter, 1
.Op(Instruction.SWAP1) // 1, counter
.Op(Instruction.SUB) // counter-1
.Op(Instruction.DUP1) // counter-1, counter-1
.PushData(1 + repeat.Length) // counter-1, counter-1, 2
.Op(Instruction.JUMPI); // counter-1

if (isIL)
{
// differentiate by adding one point
code = code.Op(Instruction.POP);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this difference needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember.

}

// warmup
Execute(BlockNumber, 10_000, code.Done);

Stopwatch sw = Stopwatch.StartNew();

TestAllTracerWithOutput result = Execute(BlockNumber, 7_000_000, code.Done);

const int n = 1_000;

Console.WriteLine($"Execution of {loopCount} took {sw.Elapsed} taking {((double)sw.ElapsedMilliseconds * n) / loopCount}ms per {n} spins");
Console.WriteLine(result.Error);
}
}
27 changes: 16 additions & 11 deletions src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2021 Demerzel Solutions Limited
// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
Expand All @@ -15,9 +15,6 @@
// along with the Nethermind. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections;
using System.Reflection.PortableExecutable;
using System.Threading;
using Nethermind.Evm.Precompiles;

namespace Nethermind.Evm.CodeAnalysis
Expand All @@ -27,22 +24,30 @@ public class CodeInfo
private const int SampledCodeLength = 10_001;
private const int PercentageOfPush1 = 40;
private const int NumberOfSamples = 100;
private static Random _rand = new();

public byte[] MachineCode { get; set; }
public IPrecompile? Precompile { get; set; }
public byte[] MachineCode { get; }
public IPrecompile? Precompile => _data as IPrecompile;

/// <summary>
/// Raw additional data stored for this.
/// </summary>
public object? Data => _data;

private readonly object? _data;

private ICodeInfoAnalyzer? _analyzer;

public CodeInfo(byte[] code)
public CodeInfo(byte[] code, object? data = null)
{
MachineCode = code;
_data = data;
}

public bool IsPrecompile => Precompile != null;
public bool IsPrecompile => _data is IPrecompile;

public CodeInfo(IPrecompile precompile)
{
Precompile = precompile;
_data = precompile;
MachineCode = Array.Empty<byte>();
}

Expand All @@ -69,7 +74,7 @@ private void CreateAnalyzer()
// we check (by sampling randomly) how many PUSH1 instructions are in the code
for (int i = 0; i < NumberOfSamples; i++)
{
byte instruction = MachineCode[_rand.Next(0, MachineCode.Length)];
byte instruction = MachineCode[Random.Shared.Next(0, MachineCode.Length)];

// PUSH1
if (instruction == 0x60)
Expand Down