Skip to content

Commit

Permalink
IL based vm
Browse files Browse the repository at this point in the history
  • Loading branch information
Scooletz committed Apr 2, 2022
1 parent 6c3915e commit 1fb723d
Show file tree
Hide file tree
Showing 10 changed files with 2,821 additions and 1,676 deletions.
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);
}

// 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

0 comments on commit 1fb723d

Please sign in to comment.