diff --git a/src/Nethermind/Directory.Packages.props b/src/Nethermind/Directory.Packages.props index e3658227674..4a33d67912e 100644 --- a/src/Nethermind/Directory.Packages.props +++ b/src/Nethermind/Directory.Packages.props @@ -69,6 +69,7 @@ + diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeAnalysis/IlEvmTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeAnalysis/IlEvmTests.cs new file mode 100644 index 00000000000..18b7781a3f6 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/CodeAnalysis/IlEvmTests.cs @@ -0,0 +1,440 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.CodeAnalysis.IL; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; +using Nethermind.Specs; +using Nethermind.State; +using NUnit.Framework; +using Sigil; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using static Nethermind.Evm.Tracing.GethStyle.Custom.JavaScript.Log; + +namespace Nethermind.Evm.Test.CodeAnalysis +{ + public class P01P01ADD : InstructionChunk + { + public static byte[] Pattern => [96, 96, 01]; + public byte CallCount { get; set; } = 0; + + public void Invoke(EvmState vmState, IReleaseSpec spec, ref int programCounter, ref long gasAvailable, ref EvmStack stack) where T : struct, VirtualMachine.IIsTracing + { + CallCount++; + UInt256 lhs = vmState.Env.CodeInfo.MachineCode.Span[programCounter + 1]; + UInt256 rhs = vmState.Env.CodeInfo.MachineCode.Span[programCounter + 3]; + stack.PushUInt256(lhs + rhs); + } + } + + [TestFixture] + public class IlEvmTests : VirtualMachineTestsBase + { + private const string AnalyzerField = "_analyzer"; + + [SetUp] + public override void Setup() + { + base.Setup(); + IlAnalyzer.AddPattern(P01P01ADD.Pattern, new P01P01ADD()); + } + + [Test] + public async Task Pattern_Analyzer_Find_All_Instance_Of_Pattern() + { + byte[] bytecode = + Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .ADD() + .PushSingle(42) + .PushSingle(5) + .ADD() + .Done; + + CodeInfo codeInfo = new CodeInfo(bytecode); + + await IlAnalyzer.StartAnalysis(codeInfo, IlInfo.ILMode.PatternMatching); + + codeInfo.IlInfo.Chunks.Count.Should().Be(2); + } + + [Test] + public void Execution_Swap_Happens_When_Pattern_Occurs() + { + P01P01ADD pattern = IlAnalyzer.GetPatternHandler(P01P01ADD.Pattern); + + byte[] bytecode = + Prepare.EvmCode + .JUMPDEST() + .PushSingle(23) + .PushSingle(7) + .ADD() + .PushSingle(42) + .PushSingle(5) + .ADD() + .JUMP(0) + .Done; + + /* + byte[] initcode = + Prepare.EvmCode + .StoreDataInMemory(0, bytecode) + .Return(bytecode.Length, 0) + .Done; + + byte[] code = + Prepare.EvmCode + .PushData(0) + .PushData(0) + .PushData(0) + .PushData(0) + .PushData(0) + .Create(initcode, 1) + .PushData(1000) + .CALL() + .Done; + var address = receipts.TxReceipts[0].ContractAddress; + */ + + for (int i = 0; i < IlAnalyzer.CompoundOpThreshold * 2; i++) + { + ExecuteBlock(new NullBlockTracer(), bytecode); + } + + Assert.Greater(pattern.CallCount, 0); + } + + [Test] + public void Pure_Opcode_Emition_Coveraga() + { + Instruction[] instructions = + System.Enum.GetValues() + .Where(opcode => !opcode.IsStateful()) + .ToArray(); + + + List<(Instruction, Exception)> notYetImplemented = []; + foreach (var instruction in instructions) + { + string name = $"ILEVM_TEST_{instruction}"; + OpcodeInfo opcode = new OpcodeInfo(0, instruction, null); + try + { + ILCompiler.CompileSegment(name, [opcode], []); + } + catch (NotSupportedException nse) + { + notYetImplemented.Add((instruction, nse)); + } + catch (Exception) + { + } + } + + string missingOpcodes = String.Join("; ", notYetImplemented.Select(op => op.Item1.ToString())); + Assert.That(notYetImplemented.Count == 0, $"{notYetImplemented.Count} opcodes missing: [{missingOpcodes}]"); + } + + + public static IEnumerable<(Instruction?, byte[])> GetBytecodes() + { + yield return (null, Prepare.EvmCode + .Done); + yield return (Instruction.PUSH32, Prepare.EvmCode + .PushSingle(1) + .Done); + yield return (Instruction.ISZERO, Prepare.EvmCode + .ISZERO(7) + .ISZERO(0) + .ISZERO(7) + .Done); + yield return(Instruction.SUB, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .SUB() + .Done); + + yield return (Instruction.ADD, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .ADD() + .Done); + + yield return (Instruction.ADDMOD, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .PushSingle(5) + .ADDMOD() + .Done); + + yield return(Instruction.MUL, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .MUL() + .Done); + + yield return(Instruction.EXP, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .EXP() + .Done); + + yield return(Instruction.MOD, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .MOD() + .Done); + + yield return(Instruction.DIV, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .DIV() + .Done); + + yield return(Instruction.MSTORE, Prepare.EvmCode + .MSTORE(0, ((UInt256)23).PaddedBytes(32)) + .Done); + + yield return(Instruction.MLOAD, Prepare.EvmCode + .MSTORE(0, ((UInt256)23).PaddedBytes(32)) + .MLOAD(0) + .Done); + + yield return(Instruction.MCOPY, Prepare.EvmCode + .MSTORE(0, ((UInt256)23).PaddedBytes(32)) + .MCOPY(32, 0, 32) + .Done); + + yield return(Instruction.EQ, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .EQ() + .Done); + + yield return(Instruction.GT, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .GT() + .Done); + + yield return(Instruction.LT, Prepare.EvmCode + .PushSingle(23) + .PushSingle(7) + .LT() + .Done); + + yield return (Instruction.NOT, Prepare.EvmCode + .PushSingle(1) + .NOT() + .Done); + + yield return (Instruction.BLOBHASH, Prepare.EvmCode + .PushSingle(0) + .BLOBHASH() + .Done); + + yield return (Instruction.BLOCKHASH, Prepare.EvmCode + .BLOCKHASH(0) + .Done); + + yield return (Instruction.CALLDATACOPY, Prepare.EvmCode + .CALLDATACOPY(0, 0, 32) + .Done); + + yield return (Instruction.CALLDATALOAD, Prepare.EvmCode + .CALLDATALOAD(0) + .Done); + + yield return (Instruction.MSIZE, Prepare.EvmCode + .MSIZE() + .Done); + + yield return (Instruction.GASPRICE, Prepare.EvmCode + .GASPRICE() + .Done); + + yield return (Instruction.CODESIZE, Prepare.EvmCode + .CODESIZE() + .Done); + + yield return (Instruction.PC, Prepare.EvmCode + .PC() + .Done); + + yield return (Instruction.COINBASE, Prepare.EvmCode + .COINBASE() + .Done); + + yield return (Instruction.TIMESTAMP, Prepare.EvmCode + .TIMESTAMP() + .Done); + + yield return (Instruction.NUMBER, Prepare.EvmCode + .NUMBER() + .Done); + + yield return (Instruction.GASLIMIT, Prepare.EvmCode + .GASLIMIT() + .Done); + + yield return (Instruction.CALLER, Prepare.EvmCode + .CALLER() + .Done); + + yield return (Instruction.ADDRESS, Prepare.EvmCode + .ADDRESS() + .Done); + + yield return (Instruction.ORIGIN, Prepare.EvmCode + .ORIGIN() + .Done); + + yield return (Instruction.CALLVALUE, Prepare.EvmCode + .CALLVALUE() + .Done); + + yield return (Instruction.CHAINID, Prepare.EvmCode + .CHAINID() + .Done); + + yield return (Instruction.GAS,Prepare.EvmCode + .GAS() + .Done); + + yield return (Instruction.RETURNDATASIZE, Prepare.EvmCode + .RETURNDATASIZE() + .Done); + + yield return (Instruction.BASEFEE, Prepare.EvmCode + .BASEFEE() + .Done); + + yield return (Instruction.RETURN, Prepare.EvmCode + .RETURN(0, 32) + .Done); + + yield return (Instruction.REVERT, Prepare.EvmCode + .REVERT(0, 32) + .Done); + + yield return (Instruction.CALLDATASIZE, Prepare.EvmCode + .CALLDATASIZE() + .Done); + + yield return (Instruction.JUMPI, Prepare.EvmCode + .PushSingle(23) + .PushSingle(1) + .JUMPI(9) + .PushSingle(3) + .JUMPDEST() + .PushSingle(0) + .MUL() + .Done); + + yield return (Instruction.JUMP, Prepare.EvmCode + .PushSingle(23) + .JUMP(10) + .JUMPDEST() + .PushSingle(3) + .MUL() + .STOP() + .JUMPDEST() + .JUMP(5) + .Done); + + yield return (Instruction.SHL, Prepare.EvmCode + .PushSingle(23) + .PushSingle(1) + .SHL() + .Done); + + yield return (Instruction.SHR, Prepare.EvmCode + .PushSingle(23) + .PushSingle(1) + .SHR() + .Done); + + yield return (Instruction.SAR, Prepare.EvmCode + .PushSingle(23) + .PushSingle(1) + .SAR() + .Done); + + yield return (Instruction.AND, Prepare.EvmCode + .PushSingle(23) + .PushSingle(1) + .AND() + .Done); + + yield return (Instruction.OR, Prepare.EvmCode + .PushSingle(23) + .PushSingle(1) + .OR() + .Done); + + yield return (Instruction.XOR, Prepare.EvmCode + .PushSingle(23) + .PushSingle(1) + .XOR() + .Done); + + yield return (Instruction.SLT, Prepare.EvmCode + .PushData(23) + .PushSingle(4) + .SLT() + .Done); + + yield return (Instruction.SGT, Prepare.EvmCode + .PushData(23) + .PushData(1) + .SGT() + .Done); + + yield return (Instruction.BYTE, Prepare.EvmCode + .BYTE(16, UInt256.MaxValue.PaddedBytes(32)) + .Done); + + yield return (Instruction.JUMP, Prepare.EvmCode + .JUMP(31) + .Done); + } + + [Test, TestCaseSource(nameof(GetBytecodes))] + public void Ensure_Evm_ILvm_Compatibility((Instruction? opcode, byte[] bytecode) testcase) + { + var blkExCtx = new BlockExecutionContext(BuildBlock(MainnetSpecProvider.CancunActivation, SenderRecipientAndMiner.Default).Header); + var txExCtx = new TxExecutionContext(blkExCtx, TestItem.AddressA, 23, [TestItem.KeccakH.Bytes.ToArray()]); + var envExCtx = new ExecutionEnvironment(new CodeInfo(testcase.bytecode), Recipient, Sender, Contract, new ReadOnlyMemory([1, 2, 3, 4, 5, 6, 7]), txExCtx, 23, 7); + var stack = new byte[1024 * 32]; + var inputBuffer = envExCtx.InputData; + var returnBuffer = ReadOnlyMemory.Empty; + + var state = new EvmState( + 1_000_000, + new ExecutionEnvironment(new CodeInfo(testcase.bytecode), Address.FromNumber(1), Address.FromNumber(1), Address.FromNumber(1), ReadOnlyMemory.Empty, txExCtx, 0, 0), + ExecutionType.CALL, + isTopLevel: false, + Snapshot.Empty, + isContinuation: false); + + state.InitStacks(); + + ILEvmState iLEvmState = new ILEvmState(state, EvmExceptionType.None, 0, 100000, ref returnBuffer); + var metadata = IlAnalyzer.StripByteCode(testcase.bytecode); + var ctx = ILCompiler.CompileSegment("ILEVM_TEST", metadata.Item1, metadata.Item2); + ctx.Method(ref iLEvmState, MainnetSpecProvider.Instance, _blockhashProvider, ctx.Data); + Assert.IsTrue(iLEvmState.EvmException == EvmExceptionType.None); + } + + + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index dcddc35cfec..1b36ba47c2c 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -31,6 +31,7 @@ public class VirtualMachineTestsBase protected const long DefaultBlockGasLimit = 8000000; private IEthereumEcdsa _ethereumEcdsa; + protected IBlockhashProvider _blockhashProvider; protected ITransactionProcessor _processor; private IDb _stateDb; diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index e1bb762bceb..fced3d14ff7 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -3,7 +3,8 @@ using System; using System.Threading; - +using Nethermind.Evm.CodeAnalysis.IL; +using System.Runtime.CompilerServices; using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.CodeAnalysis @@ -12,6 +13,23 @@ public class CodeInfo : IThreadPoolWorkItem { public ReadOnlyMemory MachineCode { get; } public IPrecompile? Precompile { get; set; } + + + // IL-EVM + private int _callCount; + + public async void NoticeExecution() + { + // IL-EVM info already created + if (_callCount > IlAnalyzer.IlCompilerThreshold) + return; + + // use Interlocked just in case of concurrent execution to run it only once + IlInfo.ILMode mode = Interlocked.Increment(ref _callCount) == IlAnalyzer.CompoundOpThreshold + ? IlInfo.ILMode.PatternMatching + : _callCount == IlAnalyzer.IlCompilerThreshold ? IlInfo.ILMode.SubsegmentsCompiling : IlInfo.ILMode.NoIlvm; + await IlAnalyzer.StartAnalysis(this, mode); + } private readonly JumpDestinationAnalyzer _analyzer; private static readonly JumpDestinationAnalyzer _emptyAnalyzer = new(Array.Empty()); public static CodeInfo Empty { get; } = new CodeInfo(Array.Empty()); @@ -31,6 +49,11 @@ public CodeInfo(ReadOnlyMemory code) public bool IsPrecompile => Precompile is not null; public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer) && !IsPrecompile; + /// + /// Gets information whether this code info has IL-EVM optimizations ready. + /// + internal IlInfo? IlInfo { get; set; } = IlInfo.Empty; + public CodeInfo(IPrecompile precompile) { Precompile = precompile; diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILCompiler.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILCompiler.cs new file mode 100644 index 00000000000..7933ce36916 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILCompiler.cs @@ -0,0 +1,1739 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Evm.IL; +using Nethermind.Int256; +using Sigil; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; +using Label = Sigil.Label; +using static Nethermind.Evm.IL.EmitExtensions; +using MathGmp.Native; +using Nethermind.Evm.Tracing.GethStyle.Custom.Native.FourByte; +using System.Drawing; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Specs; + +namespace Nethermind.Evm.CodeAnalysis.IL; +internal class ILCompiler +{ + public delegate void ExecuteSegment(ref ILEvmState state, ISpecProvider spec, IBlockhashProvider BlockhashProvider, byte[][] immediatesData); + public class SegmentExecutionCtx + { + public ExecuteSegment Method; + public byte[][] Data; + } + public static SegmentExecutionCtx CompileSegment(string segmentName, OpcodeInfo[] code, byte[][] data) + { + // code is optimistic assumes stack underflow and stack overflow to not occure (WE NEED EOF FOR THIS) + // Note(Ayman) : What stops us from adopting stack analysis from EOF in ILVM? + // Note(Ayman) : verify all endianness arguments and bytes + + Emit method = Emit.NewDynamicMethod(segmentName, doVerify: true, strictBranchVerification: true); + + if (code.Length == 0) + { + method.Return(); + } + else + { + EmitSegmentBody(method, code); + } + + ExecuteSegment dynEmitedDelegate = method.CreateDelegate(); + return new SegmentExecutionCtx + { + Method = dynEmitedDelegate, + Data = data + }; + } + + private static void EmitSegmentBody(Emit method, OpcodeInfo[] code) + { + + using Local jmpDestination = method.DeclareLocal(Word.Int0Field.FieldType); + using Local consumeJumpCondition = method.DeclareLocal(typeof(int)); + + using Local address = method.DeclareLocal(typeof(Address)); + using Local hash256 = method.DeclareLocal(typeof(Hash256)); + + using Local uint256A = method.DeclareLocal(typeof(UInt256)); + using Local uint256B = method.DeclareLocal(typeof(UInt256)); + using Local uint256C = method.DeclareLocal(typeof(UInt256)); + using Local uint256R = method.DeclareLocal(typeof(UInt256)); + + using Local localReadonOnlySpan = method.DeclareLocal(typeof(ReadOnlySpan)); + using Local localZeroPaddedSpan = method.DeclareLocal(typeof(ZeroPaddedSpan)); + using Local localSpan = method.DeclareLocal(typeof(Span)); + using Local localArray = method.DeclareLocal(typeof(byte[])); + using Local uint64A = method.DeclareLocal(typeof(ulong)); + + using Local uint32A = method.DeclareLocal(typeof(uint)); + using Local int64A = method.DeclareLocal(typeof(long)); + using Local byte8A = method.DeclareLocal(typeof(byte)); + using Local buffer = method.DeclareLocal(typeof(byte*)); + + using Local gasAvailable = method.DeclareLocal(typeof(long)); + using Local programCounter = method.DeclareLocal(typeof(ushort)); + + using Local stack = method.DeclareLocal(typeof(Span)); + using Local head = method.DeclareLocal(typeof(int)); + + // allocate stack + method.LoadArgument(0); + method.Duplicate(); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Stack))); + method.Call(GetCastMethodInfo()); + method.StoreLocal(stack); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.StackHead))); + method.StoreLocal(head); + + // set gas to local + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.GasAvailable))); + method.Convert(); + method.StoreLocal(gasAvailable); + + // set pc to local + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ProgramCounter))); + method.StoreLocal(programCounter); + + Dictionary evmExceptionLabels = new(); + + foreach (var exception in Enum.GetValues()) + { + evmExceptionLabels.Add(exception, method.DefineLabel()); + } + + Label exit = method.DefineLabel(); // the label just before return + Label jumpTable = method.DefineLabel(); // jump table + Label ret = method.DefineLabel(); + + Dictionary jumpDestinations = new(); + + + // Idea(Ayman) : implement every opcode as a method, and then inline the IL of the method in the main method + + Dictionary gasCost = BuildCostLookup(code); + for (int i = 0; i < code.Length; i++) + { + OpcodeInfo op = code[i]; + + + if (op.Operation is Instruction.JUMPDEST) + { + // mark the jump destination + jumpDestinations[op.ProgramCounter] = method.DefineLabel(); + method.MarkLabel(jumpDestinations[op.ProgramCounter]); + method.LoadConstant(op.ProgramCounter); + method.StoreLocal(programCounter); + continue; + } + // set pc + method.LoadConstant(op.ProgramCounter); + method.StoreLocal(programCounter); + + // load gasAvailable + method.LoadLocal(gasAvailable); + + // get pc gas cost + method.LoadConstant(op.Metadata?.GasCost ?? 0); + + // subtract the gas cost + method.Subtract(); + // check if gas is available + method.Duplicate(); + method.StoreLocal(gasAvailable); + method.LoadConstant((long)0); + + // if gas is not available, branch to out of gas + method.BranchIfLess(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + // else emit + switch (op.Operation) + { + case Instruction.STOP: + method.LoadArgument(0); + method.LoadConstant(true); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ShouldStop))); + method.Branch(ret); + break; + case Instruction.INVALID: + method.Branch(evmExceptionLabels[EvmExceptionType.InvalidCode]); + break; + case Instruction.CHAINID: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(1); + method.CallVirtual(GetPropertyInfo(typeof(ISpecProvider), nameof(ISpecProvider.ChainId), false, out _)); + method.StoreField(Word.Ulong0Field); + method.StackPush(head); + break; + case Instruction.NOT: + method.Load(stack, head); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackPop(head); + + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256R); + method.Call(typeof(UInt256).GetMethod(nameof(UInt256.Not))); + method.Load(stack, head); + method.LoadLocal(uint256R); + method.Call(Word.SetUInt256); + break; + case Instruction.JUMP: + // we jump into the jump table + method.Branch(jumpTable); + break; + case Instruction.JUMPI: + // consume the jump condition + Label noJump = method.DefineLabel(); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetIsZero); + + // if the jump condition is false, we do not jump + method.BranchIfTrue(noJump); + + // load the jump address + method.LoadConstant(1); + method.StoreLocal(consumeJumpCondition); + + // we jump into the jump table + method.Branch(jumpTable); + + method.MarkLabel(noJump); + method.StackPop(head, 2); + break; + case Instruction.PUSH0: + method.CleanWord(stack, head); + method.Load(stack, head); + method.Call(Word.SetToZero); + method.StackPush(head); + break; + case Instruction.PUSH1: + case Instruction.PUSH2: + case Instruction.PUSH3: + case Instruction.PUSH4: + case Instruction.PUSH5: + case Instruction.PUSH6: + case Instruction.PUSH7: + case Instruction.PUSH8: + case Instruction.PUSH9: + case Instruction.PUSH10: + case Instruction.PUSH11: + case Instruction.PUSH12: + case Instruction.PUSH13: + case Instruction.PUSH14: + case Instruction.PUSH15: + case Instruction.PUSH16: + case Instruction.PUSH17: + case Instruction.PUSH18: + case Instruction.PUSH19: + case Instruction.PUSH20: + case Instruction.PUSH21: + case Instruction.PUSH22: + case Instruction.PUSH23: + case Instruction.PUSH24: + case Instruction.PUSH25: + case Instruction.PUSH26: + case Instruction.PUSH27: + case Instruction.PUSH28: + case Instruction.PUSH29: + case Instruction.PUSH30: + case Instruction.PUSH31: + case Instruction.PUSH32: + // we load the stack + method.CleanWord(stack, head); + method.Load(stack, head); + + // we load the span of bytes + method.LoadArgument(3); + method.LoadConstant(op.Arguments.Value); + method.LoadElement(); + method.Call(typeof(ReadOnlySpan).GetMethod("op_Implicit", new[] { typeof(byte[]) })); + method.StoreLocal(localReadonOnlySpan); + + // we call UInt256 constructor taking a span of bytes and a bool + method.LoadLocalAddress(localReadonOnlySpan); + method.LoadConstant(BitConverter.IsLittleEndian); + method.NewObject(typeof(UInt256), typeof(ReadOnlySpan).MakeByRefType(), typeof(bool)); + + // we store the UInt256 in the stack + method.Call(Word.SetUInt256); + method.StackPush(head); + break; + case Instruction.ADD: + EmitBinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Add), BindingFlags.Public | BindingFlags.Static)!, null, uint256A, uint256B); + break; + case Instruction.SUB: + EmitBinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Subtract), BindingFlags.Public | BindingFlags.Static)!, null, uint256A, uint256B); + break; + case Instruction.MUL: + EmitBinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Multiply), BindingFlags.Public | BindingFlags.Static)!, null, uint256A, uint256B); + break; + case Instruction.MOD: + EmitBinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Mod), BindingFlags.Public | BindingFlags.Static)!, + (il, postInstructionLabel, locals) => + { + Label label = il.DefineLabel(); + + il.LoadLocalAddress(locals[1]); + il.Call(GetPropertyInfo(typeof(UInt256), nameof(UInt256.IsZero), false, out _)); + il.BranchIfFalse(label); + + il.LoadConstant(0); + il.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + il.StoreLocal(uint256R); + il.Branch(postInstructionLabel); + + il.MarkLabel(label); + }, uint256A, uint256B); + break; + case Instruction.SMOD: + EmitBinaryInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(Int256.Int256.Mod), BindingFlags.Public | BindingFlags.Static)!, + (il, postInstructionLabel, locals) => + { + Label label = il.DefineLabel(); + + il.LoadLocalAddress(locals[1]); + il.Call(GetPropertyInfo(typeof(UInt256), nameof(UInt256.IsZeroOrOne), false, out _)); + il.BranchIfFalse(label); + + il.LoadConstant(0); + il.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + il.StoreLocal(uint256R); + il.Branch(postInstructionLabel); + + il.MarkLabel(label); + }, uint256A, uint256B); + break; + case Instruction.DIV: + EmitBinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Divide), BindingFlags.Public | BindingFlags.Static)!, + (il, postInstructionLabel, locals) => + { + Label label = il.DefineLabel(); + + il.LoadLocalAddress(locals[1]); + il.Call(GetPropertyInfo(typeof(UInt256), nameof(UInt256.IsZero), false, out _)); + il.BranchIfFalse(label); + + il.LoadConstant(0); + il.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + il.StoreLocal(uint256R); + il.Branch(postInstructionLabel); + + il.MarkLabel(label); + }, uint256A, uint256B); + break; + case Instruction.SDIV: + EmitBinaryInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(Int256.Int256.Divide), BindingFlags.Public | BindingFlags.Static)!, + (il, postInstructionLabel, locals) => + { + Label label1 = il.DefineLabel(); + Label label2 = il.DefineLabel(); + + il.LoadLocalAddress(locals[1]); + il.Call(GetPropertyInfo(typeof(UInt256), nameof(UInt256.IsZeroOrOne), false, out _)); + il.BranchIfFalse(label1); + + il.LoadConstant(0); + il.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + il.StoreLocal(uint256R); + il.Branch(postInstructionLabel); + + il.MarkLabel(label1); + + il.LoadLocalAddress(locals[1]); + il.Call(GetAsMethodInfo()); + il.LoadField(typeof(Int256.Int256).GetField(nameof(Int256.Int256.MinusOne), BindingFlags.Static | BindingFlags.Public)); + il.Call(typeof(Int256.Int256).GetMethod("op_Equality")); + + il.LoadLocalAddress(locals[0]); + il.Call(typeof(VirtualMachine).GetProperty(nameof(VirtualMachine.P255), BindingFlags.Static).GetMethod); + il.Call(typeof(Int256.Int256).GetMethod("op_Equality")); + il.And(); + il.BranchIfFalse(label2); + + il.Call(typeof(VirtualMachine).GetProperty(nameof(VirtualMachine.P255), BindingFlags.Static).GetMethod); + il.StoreLocal(uint256R); + il.Branch(postInstructionLabel); + + il.MarkLabel(label2); + }, uint256A, uint256B); + break; + case Instruction.ADDMOD: + EmitTrinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.AddMod), BindingFlags.Public | BindingFlags.Static)!, + (il, postInstructionLabel, locals) => + { + Label label = il.DefineLabel(); + + il.LoadLocalAddress(locals[2]); + il.Call(GetPropertyInfo(typeof(UInt256), nameof(UInt256.IsZeroOrOne), false, out _)); + il.BranchIfFalse(label); + + il.LoadConstant(0); + il.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + il.StoreLocal(uint256R); + il.Branch(postInstructionLabel); + + il.MarkLabel(label); + }, uint256A, uint256B, uint256C); + break; + case Instruction.MULMOD: + EmitTrinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.MultiplyMod), BindingFlags.Public | BindingFlags.Static)!, + (il, postInstructionLabel, locals) => + { + Label label = il.DefineLabel(); + + il.LoadLocalAddress(locals[2]); + il.Call(GetPropertyInfo(typeof(UInt256), nameof(UInt256.IsZeroOrOne), false, out _)); + il.BranchIfFalse(label); + + il.LoadConstant(0); + il.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + il.StoreLocal(uint256R); + il.Branch(postInstructionLabel); + + il.MarkLabel(label); + }, uint256A, uint256B, uint256C); + break; + case Instruction.SHL: + EmitShiftUInt256Method(method, uint256R, (stack, head), isLeft: true, uint256A, uint256B); + break; + case Instruction.SHR: + EmitShiftUInt256Method(method, uint256R, (stack, head), isLeft: false, uint256A, uint256B); + break; + case Instruction.SAR: + EmitShiftInt256Method(method, uint256R, (stack, head), uint256A, uint256B); + break; + case Instruction.AND: + EmitBitwiseUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.And), BindingFlags.Public | BindingFlags.Static)!, uint256A, uint256B); + break; + case Instruction.OR: + EmitBitwiseUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Or), BindingFlags.Public | BindingFlags.Static)!, uint256A, uint256B); + break; + case Instruction.XOR: + EmitBitwiseUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Xor), BindingFlags.Public | BindingFlags.Static)!, uint256A, uint256B); + break; + case Instruction.EXP: + EmitBinaryUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod(nameof(UInt256.Exp), BindingFlags.Public | BindingFlags.Static)!, null, uint256A, uint256B); + break; + case Instruction.LT: + EmitComparaisonUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod("op_LessThan", new[] { typeof(UInt256).MakeByRefType(), typeof(UInt256).MakeByRefType() }), uint256A, uint256B); + break; + case Instruction.GT: + EmitComparaisonUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod("op_GreaterThan", new[] { typeof(UInt256).MakeByRefType(), typeof(UInt256).MakeByRefType() }), uint256A, uint256B); + break; + case Instruction.SLT: + EmitComparaisonInt256Method(method, uint256R, (stack, head), typeof(Int256.Int256).GetMethod(nameof(Int256.Int256.CompareTo), new[] { typeof(Int256.Int256) }), false, uint256A, uint256B); + break; + case Instruction.SGT: + EmitComparaisonInt256Method(method, uint256R, (stack, head), typeof(Int256.Int256).GetMethod(nameof(Int256.Int256.CompareTo), new[] { typeof(Int256.Int256) }), true, uint256A, uint256B); + break; + case Instruction.EQ: + EmitComparaisonUInt256Method(method, uint256R, (stack, head), typeof(UInt256).GetMethod("op_Equality", new[] { typeof(UInt256).MakeByRefType(), typeof(UInt256).MakeByRefType() }), uint256A, uint256B); + break; + case Instruction.ISZERO: + // we load the stack + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetIsZero); + method.StackPop(head, 1); + method.StoreLocal(byte8A); + + // we convert the result to a Uint256 and store it in the stack + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocal(byte8A); + method.StoreField(GetFieldInfo(typeof(Word), nameof(Word.Byte0))); + method.StackPush(head); + break; + case Instruction.POP: + method.StackPop(head); + break; + case Instruction.DUP1: + case Instruction.DUP2: + case Instruction.DUP3: + case Instruction.DUP4: + case Instruction.DUP5: + case Instruction.DUP6: + case Instruction.DUP7: + case Instruction.DUP8: + case Instruction.DUP9: + case Instruction.DUP10: + case Instruction.DUP11: + case Instruction.DUP12: + case Instruction.DUP13: + case Instruction.DUP14: + case Instruction.DUP15: + case Instruction.DUP16: + int count = (int)op.Operation - (int)Instruction.DUP1 + 1; + method.Load(stack, head); + method.StackLoadPrevious(stack, head, count); + method.LoadObject(typeof(Word)); + method.StoreObject(typeof(Word)); + method.StackPush(head); + break; + case Instruction.SWAP1: + case Instruction.SWAP2: + case Instruction.SWAP3: + case Instruction.SWAP4: + case Instruction.SWAP5: + case Instruction.SWAP6: + case Instruction.SWAP7: + case Instruction.SWAP8: + case Instruction.SWAP9: + case Instruction.SWAP10: + case Instruction.SWAP11: + case Instruction.SWAP12: + case Instruction.SWAP13: + case Instruction.SWAP14: + case Instruction.SWAP15: + case Instruction.SWAP16: + count = (int)op.Operation - (int)Instruction.SWAP1 + 1; + + method.LoadLocalAddress(uint256R); + method.StackLoadPrevious(stack, head, 1); + method.LoadObject(typeof(Word)); + method.StoreObject(typeof(Word)); + + method.StackLoadPrevious(stack, head, 1); + method.StackLoadPrevious(stack, head, count); + method.LoadObject(typeof(Word)); + method.StoreObject(typeof(Word)); + + method.StackLoadPrevious(stack, head, count); + method.LoadLocalAddress(uint256R); + method.LoadObject(typeof(Word)); + method.StoreObject(typeof(Word)); + break; + + // Note(Ayman): following opcode need double checking + // is pushing to stack happening correctly + case Instruction.CODESIZE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadConstant(code.Length); + method.StoreField(GetFieldInfo(typeof(Word), nameof(Word.UInt0))); + method.StackPush(head); + break; + case Instruction.PC: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocal(programCounter); + method.StoreField(GetFieldInfo(typeof(Word), nameof(Word.UInt0))); + method.StackPush(head); + break; + case Instruction.COINBASE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); + method.Call(GetPropertyInfo(nameof(BlockHeader.GasBeneficiary), false, out _)); + method.Call(Word.SetAddress); + method.StackPush(head); + break; + case Instruction.TIMESTAMP: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); + method.Call(GetPropertyInfo(nameof(BlockHeader.Timestamp), false, out _)); + method.StoreField(Word.Ulong0Field); + method.StackPush(head); + break; + case Instruction.NUMBER: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); + method.Call(GetPropertyInfo(nameof(BlockHeader.Number), false, out _)); + method.StoreField(Word.Ulong0Field); + method.StackPush(head); + break; + case Instruction.GASLIMIT: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); + method.Call(GetPropertyInfo(nameof(BlockHeader.GasLimit), false, out _)); + method.StoreField(Word.Ulong0Field); + method.StackPush(head); + break; + case Instruction.CALLER: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); + method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.Caller))); + method.Call(Word.SetAddress); + method.StackPush(head); + break; + case Instruction.ADDRESS: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); + method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.ExecutingAccount))); + method.Call(Word.SetAddress); + method.StackPush(head); + break; + case Instruction.ORIGIN: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); + method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.Origin), false, out _)); + method.Call(Word.SetAddress); + method.StackPush(head); + break; + case Instruction.CALLVALUE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); + method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.Value))); + method.Call(Word.SetUInt256); + method.StackPush(head); + break; + case Instruction.GASPRICE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); + method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.GasPrice), false, out _)); + method.Call(Word.SetUInt256); + method.StackPush(head); + break; + case Instruction.CALLDATACOPY: + Label endOfOpcode = method.DefineLabel(); + + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256B); + method.StackLoadPrevious(stack, head, 3); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256C); + method.StackPop(head, 3); + + method.LoadLocal(gasAvailable); + method.LoadLocalAddress(uint256C); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadConstant(GasCostOf.Memory); + method.Multiply(); + method.LoadConstant(GasCostOf.VeryLow); + method.Add(); + method.Subtract(); + method.StoreLocal(gasAvailable); + + method.LoadLocalAddress(uint256C); + method.Call(typeof(UInt256).GetProperty(nameof(UInt256.IsZero)).GetMethod!); + method.BranchIfTrue(endOfOpcode); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256C); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.InputBuffer))); + method.LoadLocalAddress(uint256B); + method.LoadLocal(uint256C); + method.LoadField(GetFieldInfo(typeof(UInt256), nameof(UInt256.u0))); + method.Convert(); + method.LoadConstant((int)PadDirection.Right); + method.Call(typeof(ByteArrayExtensions).GetMethod(nameof(ByteArrayExtensions.SliceWithZeroPadding), [typeof(ReadOnlyMemory), typeof(UInt256).MakeByRefType(), typeof(int), typeof(PadDirection)])); + method.StoreLocal(localZeroPaddedSpan); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(localZeroPaddedSpan); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Save), [typeof(UInt256).MakeByRefType(), typeof(ZeroPaddedSpan).MakeByRefType()])); + + method.MarkLabel(endOfOpcode); + break; + case Instruction.CALLDATALOAD: + method.CleanWord(stack, head); + method.Load(stack, head); + + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StackPop(head, 1); + method.StoreLocal(uint256A); + + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.InputBuffer))); + + method.LoadLocalAddress(uint256A); + method.LoadConstant(Word.Size); + method.LoadConstant((int)PadDirection.Right); + method.Call(typeof(ByteArrayExtensions).GetMethod(nameof(ByteArrayExtensions.SliceWithZeroPadding), [typeof(ReadOnlyMemory), typeof(UInt256).MakeByRefType(), typeof(int), typeof(PadDirection)])); + method.LoadField(GetFieldInfo(typeof(ZeroPaddedSpan), nameof(ZeroPaddedSpan.Span))); + method.Call(Word.SetSpan); + method.StackPush(head); + break; + case Instruction.CALLDATASIZE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.InputBuffer))); + method.Call(GetPropertyInfo>(nameof(ReadOnlyMemory.Length), false, out _)); + method.StoreField(GetFieldInfo(typeof(Word), nameof(Word.Int0))); + method.StackPush(head); + break; + case Instruction.MSIZE: + method.CleanWord(stack, head); + method.Load(stack, head); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.Call(GetPropertyInfo(nameof(EvmPooledMemory.Size), false, out _)); + method.StoreField(GetFieldInfo(typeof(Word), nameof(Word.Ulong0))); + method.StackPush(head); + break; + case Instruction.MSTORE: + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256B); + method.StackPop(head, 2); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadConstant(Word.Size); + method.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + method.StoreLocal(uint256C); + method.LoadLocalAddress(uint256C); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256B); + method.LoadConstant(Word.Size); + method.Call(typeof(UInt256).GetMethod(nameof(UInt256.PaddedBytes))); + method.Call(typeof(Span).GetMethod("op_Implicit", new[] { typeof(byte[]) })); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.SaveWord))); + break; + case Instruction.MSTORE8: + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.LoadField(Word.Byte0Field); + method.StoreLocal(byte8A); + method.StackPop(head, 2); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadConstant(1); + method.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + method.StoreLocal(uint256C); + method.LoadLocalAddress(uint256C); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256B); + method.LoadConstant(Word.Size); + method.Call(typeof(UInt256).GetMethod(nameof(UInt256.PaddedBytes))); + method.LoadConstant(0); + method.LoadElement(); + + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.SaveByte))); + break; + case Instruction.MLOAD: + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackPop(head, 1); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadFieldAddress(GetFieldInfo(typeof(VirtualMachine), nameof(VirtualMachine.BigInt32))); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.LoadSpan), [typeof(UInt256).MakeByRefType()])); + method.Call(typeof(Span).GetMethod("op_Implicit", new[] { typeof(Span) })); + method.StoreLocal(localReadonOnlySpan); + + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocalAddress(localReadonOnlySpan); + method.LoadConstant(BitConverter.IsLittleEndian); + method.NewObject(typeof(UInt256), typeof(ReadOnlySpan).MakeByRefType(), typeof(bool)); + method.Call(Word.SetUInt256); + method.StackPush(head); + break; + case Instruction.MCOPY: + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256B); + + method.StackLoadPrevious(stack, head, 3); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256C); + + method.StackPop(head, 3); + + method.LoadLocal(gasAvailable); + method.LoadLocalAddress(uint256C); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadConstant((long)1); + method.Add(); + method.LoadConstant(GasCostOf.VeryLow); + method.Multiply(); + method.Subtract(); + method.StoreLocal(gasAvailable); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256B); + method.Call(typeof(UInt256).GetMethod(nameof(UInt256.Max))); + method.StoreLocal(uint256R); + method.LoadLocalAddress(uint256R); + method.LoadLocalAddress(uint256C); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256B); + method.LoadLocalAddress(uint256C); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.LoadSpan), [typeof(UInt256).MakeByRefType(), typeof(UInt256).MakeByRefType()])); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Save), [typeof(UInt256).MakeByRefType(), typeof(Span)])); + break; + case Instruction.KECCAK256: + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256B); + method.StackPop(head, 2); + + method.LoadLocal(gasAvailable); + method.LoadLocalAddress(uint256B); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadConstant(GasCostOf.Sha3Word); + method.Multiply(); + method.LoadConstant(GasCostOf.Sha3); + method.Add(); + method.Subtract(); + method.StoreLocal(gasAvailable); + + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256B); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256B); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.LoadSpan), [typeof(UInt256).MakeByRefType(), typeof(UInt256).MakeByRefType()])); + method.Call(typeof(ValueKeccak).GetMethod(nameof(ValueKeccak.Compute), [typeof(ReadOnlySpan)])); + method.Call(Word.SetKeccak); + method.StackPush(head); + break; + case Instruction.BYTE: + // load a + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetSpan); + method.StoreLocal(localReadonOnlySpan); + method.StackPop(head, 2); + + Label pushZeroLabel = method.DefineLabel(); + Label endOfInstructionImpl = method.DefineLabel(); + method.LoadLocalAddress(uint256A); + method.LoadConstant(Word.Size - 1); + method.Call(typeof(UInt256).GetMethod("op_GreaterThan", new[] { typeof(UInt256).MakeByRefType(), typeof(int) })); + method.LoadLocalAddress(uint256A); + method.LoadConstant(0); + method.Call(typeof(UInt256).GetMethod("op_LessThan", new[] { typeof(UInt256).MakeByRefType(), typeof(int) })); + method.Or(); + method.BranchIfTrue(pushZeroLabel); + + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocalAddress(localReadonOnlySpan); + method.LoadLocal(uint256A); + method.LoadField(GetFieldInfo(typeof(UInt256), nameof(UInt256.u0))); + method.Convert(); + method.Call(typeof(ReadOnlySpan).GetMethod("get_Item")); + method.LoadIndirect(); + method.StoreField(Word.Byte0Field); + method.StackPush(head); + method.Branch(endOfInstructionImpl); + + method.MarkLabel(pushZeroLabel); + method.CleanWord(stack, head); + method.Load(stack, head); + method.Call(Word.SetToZero); + method.StackPush(head); + + method.MarkLabel(endOfInstructionImpl); + break; + case Instruction.CODECOPY: + endOfOpcode = method.DefineLabel(); + + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256B); + method.StackLoadPrevious(stack, head, 3); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256C); + method.StackPop(head, 3); + + method.LoadLocal(gasAvailable); + method.LoadLocalAddress(uint256C); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadConstant(GasCostOf.Memory); + method.Multiply(); + method.LoadConstant(GasCostOf.VeryLow); + method.Add(); + method.Subtract(); + method.StoreLocal(gasAvailable); + + method.LoadLocalAddress(uint256C); + method.Call(typeof(UInt256).GetProperty(nameof(UInt256.IsZero)).GetMethod!); + method.BranchIfTrue(endOfOpcode); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256C); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.MachineCode))); + method.Call(typeof(ReadOnlySpan).GetMethod("op_Implicit", new[] { typeof(byte[]) })); + method.LoadLocalAddress(uint256B); + method.LoadLocal(uint256C); + method.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + method.Call(typeof(ReadOnlySpan).GetMethod(nameof(ReadOnlySpan.Slice), [typeof(UInt256).MakeByRefType(), typeof(int)])); + method.StoreLocal(localReadonOnlySpan); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(localReadonOnlySpan); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Save), [typeof(UInt256).MakeByRefType(), typeof(Span)])); + + method.MarkLabel(endOfOpcode); + break; + case Instruction.GAS: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocal(gasAvailable); + method.StoreField(Word.Ulong0Field); + method.StackPush(head); + break; + case Instruction.RETURNDATASIZE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ReturnBuffer))); + method.Call(GetPropertyInfo>(nameof(ReadOnlyMemory.Length), false, out _)); + method.StoreField(Word.Int0Field); + method.StackPush(head); + break; + case Instruction.RETURNDATACOPY: + endOfOpcode = method.DefineLabel(); + + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256B); + method.StackLoadPrevious(stack, head, 3); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256C); + method.StackPop(head, 3); + + method.LoadLocal(gasAvailable); + method.LoadLocalAddress(uint256C); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadConstant(GasCostOf.Memory); + method.Multiply(); + method.LoadConstant(GasCostOf.VeryLow); + method.Add(); + method.Subtract(); + method.StoreLocal(gasAvailable); + + // Note : check if c + b > returnData.Size + + method.LoadLocalAddress(uint256C); + method.Call(typeof(UInt256).GetProperty(nameof(UInt256.IsZero)).GetMethod!); + method.BranchIfTrue(endOfOpcode); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256C); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ReturnBuffer))); + method.Call(GetPropertyInfo(typeof(ReadOnlyMemory), nameof(ReadOnlyMemory.Span), false, out _)); + method.LoadLocalAddress(uint256B); + method.LoadLocal(uint256C); + method.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + method.Call(typeof(ReadOnlySpan).GetMethod(nameof(ReadOnlySpan.Slice), [typeof(UInt256).MakeByRefType(), typeof(int)])); + method.StoreLocal(localReadonOnlySpan); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(localReadonOnlySpan); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Save), [typeof(UInt256).MakeByRefType(), typeof(Span)])); + + method.MarkLabel(endOfOpcode); + break; + case Instruction.RETURN or Instruction.REVERT: + method.StackLoadPrevious(stack, head, 1); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetUInt256); + method.StoreLocal(uint256B); + method.StackPop(head, 2); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(gasAvailable); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256B); + method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); + method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ReturnBuffer))); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadLocalAddress(uint256A); + method.LoadLocalAddress(uint256B); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Load), [typeof(UInt256).MakeByRefType(), typeof(UInt256).MakeByRefType()])); + method.StoreObject>(); + + method.LoadArgument(0); + method.LoadConstant(true); + switch (op.Operation) + { + case Instruction.REVERT: + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ShouldRevert))); + break; + case Instruction.RETURN: + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ShouldReturn))); + break; + } + + break; + case Instruction.BASEFEE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); + method.Call(GetPropertyInfo(typeof(BlockHeader), nameof(BlockHeader.BaseFeePerGas), false, out _)); + method.Call(Word.SetUInt256); + method.StackPush(head); + break; + case Instruction.BLOBBASEFEE: + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.BlobBaseFee), false, out _)); + method.Call(GetPropertyInfo(typeof(UInt256?), nameof(Nullable.Value), false, out _)); + method.Call(Word.SetUInt256); + method.StackPush(head); + break; + case Instruction.PREVRANDAO: + Label isPostMergeBranch = method.DefineLabel(); + endOfOpcode = method.DefineLabel(); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); + method.Duplicate(); + method.Call(GetPropertyInfo(typeof(BlockHeader), nameof(BlockHeader.IsPostMerge), false, out _)); + method.BranchIfTrue(isPostMergeBranch); + method.Call(GetPropertyInfo(typeof(BlockHeader), nameof(BlockHeader.Random), false, out _)); + method.Call(GetPropertyInfo(typeof(Hash256), nameof(Hash256.Bytes), false, out _)); + method.Call(typeof(ReadOnlySpan).GetMethod("op_Implicit", new[] { typeof(Span) })); + method.StoreLocal(localReadonOnlySpan); + method.LoadLocalAddress(localReadonOnlySpan); + method.LoadConstant(BitConverter.IsLittleEndian); + method.NewObject(typeof(UInt256), typeof(ReadOnlySpan).MakeByRefType(), typeof(bool)); + method.StackPush(head); + method.Branch(endOfOpcode); + + method.MarkLabel(isPostMergeBranch); + method.Call(GetPropertyInfo(typeof(BlockHeader), nameof(BlockHeader.Difficulty), false, out _)); + method.StackPush(head); + + method.MarkLabel(endOfOpcode); + break; + case Instruction.BLOBHASH: + Label blobVersionedHashNotFound = method.DefineLabel(); + endOfOpcode = method.DefineLabel(); + + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); + method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.BlobVersionedHashes), false, out _)); + method.LoadNull(); + method.BranchIfEqual(blobVersionedHashNotFound); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); + method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.BlobVersionedHashes), false, out _)); + method.Call(GetPropertyInfo(typeof(byte[][]), nameof(Array.Length), false, out _)); + method.StackLoadPrevious(stack, head, 1); + method.LoadField(Word.Int0Field); + method.Duplicate(); + method.StoreLocal(uint32A); + method.StackPop(head, 1); + method.BranchIfLessOrEqual(blobVersionedHashNotFound); + + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); + method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.BlobVersionedHashes), false, out _)); + method.LoadLocal(uint32A); + method.LoadElement(); + method.StoreLocal(localArray); + + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocal(localArray); + method.Call(Word.SetArray); + method.Branch(endOfOpcode); + + method.MarkLabel(blobVersionedHashNotFound); + method.CleanWord(stack, head); + method.Load(stack, head); + method.Call(Word.SetToZero); + + method.MarkLabel(endOfOpcode); + method.StackPush(head, 1); + break; + case Instruction.BLOCKHASH: + Label blockHashReturnedNull = method.DefineLabel(); + Label pushToStackRegion = method.DefineLabel(); + + method.StackLoadPrevious(stack, head, 1); + method.StackPop(head, 1); + method.LoadField(Word.Ulong0Field); + method.Convert(); + method.LoadConstant(long.MaxValue); + method.Call(typeof(Math).GetMethod(nameof(Math.Min), [typeof(long), typeof(long)])); + method.StoreLocal(int64A); + + method.LoadArgument(2); + method.LoadArgument(0); + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); + method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); + method.LoadLocalAddress(int64A); + method.CallVirtual(typeof(IBlockhashProvider).GetMethod(nameof(IBlockhashProvider.GetBlockhash), [typeof(BlockHeader), typeof(long).MakeByRefType()])); + method.Duplicate(); + method.StoreLocal(hash256); + method.LoadNull(); + method.BranchIfEqual(blockHashReturnedNull); + + // not equal + method.LoadLocal(hash256); + method.Call(GetPropertyInfo(typeof(Hash256), nameof(Hash256.Bytes), false, out _)); + method.Call(typeof(Span).GetMethod("op_Implicit", new[] { typeof(Span) })); + method.StoreLocal(localReadonOnlySpan); + method.Branch(pushToStackRegion); + // equal to null + + method.MarkLabel(blockHashReturnedNull); + + method.LoadField(GetFieldInfo(typeof(VirtualMachine), nameof(VirtualMachine.BytesZero32))); + method.Call(typeof(ReadOnlySpan).GetMethod("op_Implicit", new[] { typeof(byte[]) })); + method.StoreLocal(localReadonOnlySpan); + + method.MarkLabel(pushToStackRegion); + method.CleanWord(stack, head); + method.Load(stack, head); + method.LoadLocalAddress(localReadonOnlySpan); + method.LoadConstant(BitConverter.IsLittleEndian); + method.NewObject(typeof(UInt256), typeof(ReadOnlySpan).MakeByRefType(), typeof(bool)); + method.Call(Word.SetUInt256); + method.StackPush(head); + break; + case Instruction.SIGNEXTEND: + Label signIsNegative = method.DefineLabel(); + Label endOfOpcodeHandling = method.DefineLabel(); + + method.StackLoadPrevious(stack, head, 1); + method.LoadField(Word.UInt0Field); + method.StoreLocal(uint32A); + method.StackLoadPrevious(stack, head, 2); + method.Call(Word.GetSpan); + method.StoreLocal(localSpan); + method.StackPop(head, 2); + + method.LoadConstant((uint)31); + method.LoadLocal(uint32A); + method.Subtract(); + method.StoreLocal(uint32A); + + method.LoadLocal(localArray); + method.LoadLocal(uint32A); + method.LoadElement(); + method.Convert(); + method.LoadConstant((sbyte)0); + method.BranchIfLess(signIsNegative); + + method.LoadField(GetFieldInfo(typeof(VirtualMachine), nameof(VirtualMachine.BytesZero32))); + method.Branch(endOfOpcodeHandling); + + method.MarkLabel(signIsNegative); + method.LoadField(GetFieldInfo(typeof(VirtualMachine), nameof(VirtualMachine.BytesMax32))); + + method.MarkLabel(endOfOpcodeHandling); + method.LoadConstant(0); + method.LoadLocal(uint32A); + method.Convert(); + method.Call(typeof(MemoryExtensions).GetMethod(nameof(MemoryExtensions.AsSpan), [typeof(byte[]), typeof(int), typeof(int)])); + method.LoadLocalAddress(localSpan); + method.Call(typeof(Span).GetMethod(nameof(Span.CopyTo), [typeof(Span)])); + break; + default: + throw new NotSupportedException(); + } + } + + Label skipProgramCounterSetting = method.DefineLabel(); + Local isEphemeralJump = method.DeclareLocal(); + // prepare ILEvmState + // check if returnState is null + method.MarkLabel(ret); + // we get stack size + method.LoadArgument(0); + method.LoadLocal(head); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.StackHead))); + + // set stack + method.LoadArgument(0); + method.LoadLocal(stack); + method.Call(GetCastMethodInfo()); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Stack))); + + // set gas available + method.LoadArgument(0); + method.LoadLocal(gasAvailable); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.GasAvailable))); + + // set program counter + method.LoadLocal(isEphemeralJump); + method.BranchIfTrue(skipProgramCounterSetting); + + method.LoadArgument(0); + method.LoadLocal(programCounter); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ProgramCounter))); + + method.MarkLabel(skipProgramCounterSetting); + + + // set exception + method.LoadArgument(0); + method.LoadConstant((int)EvmExceptionType.None); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmException))); + + // go to return + method.Branch(exit); + + // jump table + method.MarkLabel(jumpTable); + method.StackLoadPrevious(stack, head, 1); + method.LoadField(Word.Int0Field); + method.Call(typeof(BinaryPrimitives).GetMethod(nameof(BinaryPrimitives.ReverseEndianness), BindingFlags.Public | BindingFlags.Static, new[] { typeof(uint) }), null); + method.StoreLocal(jmpDestination); + method.StackPop(head); + + //check if jump crosses segment boundaies + Label jumpIsLocal = method.DefineLabel(); + method.LoadLocal(jmpDestination); + method.LoadConstant(code[code.Length - 1].ProgramCounter + code[code.Length - 1].Metadata?.AdditionalBytes ?? 0); + method.BranchIfLessOrEqual(jumpIsLocal); + + method.LoadArgument(0); + method.Duplicate(); + method.LoadConstant(true); + method.StoreLocal(isEphemeralJump); + method.LoadConstant(true); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ShouldJump))); + method.LoadLocal(jmpDestination); + method.Convert(); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ProgramCounter))); + method.Branch(ret); + + method.MarkLabel(jumpIsLocal); + method.StackPop(head, consumeJumpCondition); + method.LoadConstant(0); + method.StoreLocal(consumeJumpCondition); + + // if (jumpDest > uint.MaxValue) + + + + method.LoadConstant(uint.MaxValue); + method.LoadLocal(jmpDestination); + // goto invalid address + method.BranchIfGreater(evmExceptionLabels[EvmExceptionType.InvalidJumpDestination]); + // else + + const int bitMask = (1 << 4) - 1; // 128 + Label[] jumps = new Label[bitMask]; + for (int i = 0; i < bitMask; i++) + { + jumps[i] = method.DefineLabel(); + } + + // we get first Word.Size bits of the jump destination since it is less than int.MaxValue + + + method.LoadLocal(jmpDestination); + method.LoadConstant(bitMask); + method.And(); + + // switch on the first 7 bits + method.Switch(jumps); + + for (int i = 0; i < bitMask; i++) + { + method.MarkLabel(jumps[i]); + method.Print(jmpDestination); + // for each destination matching the bit mask emit check for the equality + foreach (int dest in jumpDestinations.Keys.Where(dest => (dest & bitMask) == i)) + { + method.LoadLocal(jmpDestination); + method.LoadConstant(dest); + method.Duplicate(); + method.StoreLocal(uint32A); + method.Print(uint32A); + method.BranchIfEqual(jumpDestinations[dest]); + } + method.Print(jmpDestination); + // each bucket ends with a jump to invalid access to do not fall through to another one + method.Branch(evmExceptionLabels[EvmExceptionType.InvalidCode]); + } + + foreach (var kvp in evmExceptionLabels) + { + method.MarkLabel(kvp.Value); + method.LoadArgument(0); + method.LoadConstant((int)kvp.Key); + method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmException))); + method.Branch(exit); + } + + // return + method.MarkLabel(exit); + method.Return(); + } + + private static void EmitShiftUInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, bool isLeft, params Local[] locals) + { + MethodInfo shiftOp = typeof(UInt256).GetMethod(isLeft ? nameof(UInt256.LeftShift) : nameof(UInt256.RightShift)); + Label skipPop = il.DefineLabel(); + Label endOfOpcode = il.DefineLabel(); + + // Note: Use Vector256 directoly if UInt256 does not use it internally + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + + il.LoadLocalAddress(locals[0]); + il.LoadConstant(Word.Size * sizeof(byte)); + il.Call(typeof(UInt256).GetMethod("op_GreaterThan", new[] { typeof(UInt256).MakeByRefType(), typeof(int) })); + il.BranchIfTrue(skipPop); + + il.LoadLocalAddress(locals[0]); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.LoadField(Word.Int0Field); + il.LoadLocalAddress(uint256R); + il.Call(shiftOp); + il.StackPop(stack.idx, 2); + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); + il.Call(Word.SetUInt256); + il.StackPush(stack.idx, 1); + il.Branch(endOfOpcode); + + il.MarkLabel(skipPop); + il.StackPop(stack.idx, 2); + il.CleanWord(stack.span, stack.idx); + il.StackPush(stack.idx, 1); + + il.MarkLabel(endOfOpcode); + } + + private static void EmitShiftInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, params Local[] locals) + { + Label skipPop = il.DefineLabel(); + Label signIsNeg = il.DefineLabel(); + Label endOfOpcode = il.DefineLabel(); + + // Note: Use Vector256 directoly if UInt256 does not use it internally + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + + il.LoadLocalAddress(locals[0]); + il.LoadConstant(Word.Size * sizeof(byte)); + il.Call(typeof(UInt256).GetMethod("op_GreaterThan", new[] { typeof(UInt256).MakeByRefType(), typeof(int) })); + il.BranchIfTrue(skipPop); + + il.LoadLocalAddress(locals[0]); + il.Call(GetAsMethodInfo()); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.LoadField(Word.Int0Field); + il.LoadLocalAddress(uint256R); + il.Call(GetAsMethodInfo()); + il.Call(typeof(Int256.Int256).GetMethod(nameof(Int256.Int256.RightShift), [typeof(int), typeof(Int256.Int256).MakeByRefType()])); + il.StackPop(stack.idx, 2); + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); + il.Call(Word.SetUInt256); + il.StackPush(stack.idx, 1); + il.Branch(endOfOpcode); + + il.MarkLabel(skipPop); + il.StackPop(stack.idx, 2); + + il.LoadLocalAddress(locals[0]); + il.Call(GetAsMethodInfo()); + il.Call(GetPropertyInfo(typeof(Int256.Int256), nameof(Int256.Int256.Sign), false, out _)); + il.LoadConstant(0); + il.BranchIfLess(signIsNeg); + + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.Call(Word.SetToZero); + il.StackPush(stack.idx); + il.Branch(endOfOpcode); + + // sign + il.MarkLabel(signIsNeg); + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadFieldAddress(GetFieldInfo(typeof(Int256.Int256), nameof(Int256.Int256.MinusOne))); + il.Call(GetAsMethodInfo()); + il.LoadObject(); + il.Call(Word.SetUInt256); + il.StackPush(stack.idx); + il.Branch(endOfOpcode); + + il.MarkLabel(endOfOpcode); + } + + private static void EmitBitwiseUInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, MethodInfo operation, params Local[] locals) + { + // Note: Use Vector256 directoly if UInt256 does not use it internally + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[1]); + il.StackPop(stack.idx, 2); + + // invoke op on the uint256 + il.LoadLocalAddress(locals[1]); + il.LoadLocalAddress(locals[0]); + il.LoadLocalAddress(uint256R); + il.Call(operation, null); + + // push the result to the stack + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); // stack: word*, uint256 + il.Call(Word.SetUInt256); + } + + private static void EmitComparaisonUInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, MethodInfo operation, params Local[] locals) + { + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[1]); + il.StackPop(stack.idx, 2); + + // invoke op on the uint256 + il.LoadLocalAddress(locals[1]); + il.LoadLocalAddress(locals[0]); + il.Call(operation, null); + + // convert to conv_i + il.Convert(); + il.Call(typeof(UInt256).GetMethod("op_Explicit", new[] { typeof(int) })); + il.StoreLocal(uint256R); + + // push the result to the stack + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); // stack: word*, uint256 + il.Call(Word.SetUInt256); + } + + private static void EmitComparaisonInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, MethodInfo operation, bool isGreaterThan, params Local[] locals) + { + Label endOpcodeHandling = il.DefineLabel(); + Label pushZerohandling = il.DefineLabel(); + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[1]); + il.StackPop(stack.idx, 2); + + // invoke op on the uint256 + il.LoadLocalAddress(locals[1]); + il.Call(GetAsMethodInfo()); + il.LoadLocalAddress(locals[0]); + il.Call(GetAsMethodInfo()); + il.LoadObject(); + il.Call(operation, null); + il.LoadConstant(0); + if(isGreaterThan) + { + il.BranchIfLess(pushZerohandling); + } + else + { + il.BranchIfGreater(pushZerohandling); + } + + il.LoadField(GetFieldInfo(typeof(UInt256), nameof(UInt256.One))); + il.StoreLocal(uint256R); + il.Branch(endOpcodeHandling); + + il.MarkLabel(pushZerohandling); + + il.LoadField(GetFieldInfo(typeof(UInt256), nameof(UInt256.Zero))); + il.StoreLocal(uint256R); + il.MarkLabel(endOpcodeHandling); + // push the result to the stack + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); // stack: word*, uint256 + il.Call(Word.SetUInt256); + } + + private static void EmitBinaryUInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, MethodInfo operation, Action, Label, Local[]> customHandling, params Local[] locals) + { + Label label = il.DefineLabel(); + + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[1]); + il.StackPop(stack.idx, 2); + + // incase of custom handling, we branch to the label + customHandling?.Invoke(il, label, locals); + + // invoke op on the uint256 + il.LoadLocalAddress(locals[1]); + il.LoadLocalAddress(locals[0]); + il.LoadLocalAddress(uint256R); + il.Call(operation); + + // skip the main handling + il.MarkLabel(label); + + // push the result to the stack + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); // stack: word*, uint256 + il.Call(Word.SetUInt256); + il.StackPush(stack.idx, 1); + } + + private static void EmitBinaryInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, MethodInfo operation, Action, Label, Local[]> customHandling, params Local[] locals) + { + Label label = il.DefineLabel(); + + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[1]); + il.StackPop(stack.idx, 2); + + // incase of custom handling, we branch to the label + customHandling?.Invoke(il, label, locals); + + // invoke op on the uint256 + il.LoadLocalAddress(locals[1]); + il.Call(GetAsMethodInfo()); + il.LoadLocalAddress(locals[0]); + il.Call(GetAsMethodInfo()); + il.LoadLocalAddress(uint256R); + il.Call(GetAsMethodInfo()); + il.Call(operation); + + // skip the main handling + il.MarkLabel(label); + + // push the result to the stack + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); // stack: word*, uint256 + il.Call(Word.SetUInt256); + il.StackPush(stack.idx, 1); + } + + private static void EmitTrinaryUInt256Method(Emit il, Local uint256R, (Local span, Local idx) stack, MethodInfo operation, Action, Label, Local[]> customHandling, params Local[] locals) + { + Label label = il.DefineLabel(); + + // we the two uint256 from the stack + il.StackLoadPrevious(stack.span, stack.idx, 1); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[0]); + il.StackLoadPrevious(stack.span, stack.idx, 2); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[1]); + il.StackLoadPrevious(stack.span, stack.idx, 3); + il.Call(Word.GetUInt256); + il.StoreLocal(locals[2]); + il.StackPop(stack.idx, 3); + + // incase of custom handling, we branch to the label + customHandling?.Invoke(il, label, locals); + + // invoke op on the uint256 + il.LoadLocalAddress(locals[2]); + il.LoadLocalAddress(locals[1]); + il.LoadLocalAddress(locals[0]); + il.LoadLocalAddress(uint256R); + il.Call(operation); + + // skip the main handling + il.MarkLabel(label); + + // push the result to the stack + il.CleanWord(stack.span, stack.idx); + il.Load(stack.span, stack.idx); + il.LoadLocal(uint256R); // stack: word*, uint256 + il.Call(Word.SetUInt256); + il.StackPush(stack.idx, 1); + } + + private static Dictionary BuildCostLookup(ReadOnlySpan code) + { + Dictionary costs = new(); + int costStart = 0; + long coststack = 0; + + for (int pc = 0; pc < code.Length; pc++) + { + OpcodeInfo op = code[pc]; + switch (op.Operation) + { + case Instruction.JUMPDEST: + costs[costStart] = coststack; // remember the stack chain of opcodes + costStart = pc; + coststack = op.Metadata?.GasCost ?? 0; + break; + case Instruction.JUMPI: + case Instruction.JUMP: + coststack += op.Metadata?.GasCost ?? 0; + costs[costStart] = coststack; // remember the stack chain of opcodes + costStart = pc + 1; // start with the next again + coststack = 0; + break; + default: + coststack += op.Metadata?.GasCost ?? 0; + costs[pc] = coststack; + break; + } + } + + if (coststack > 0) + { + costs[costStart] = coststack; + } + + return costs; + } +} diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILEvmState.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILEvmState.cs new file mode 100644 index 00000000000..652e2592195 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILEvmState.cs @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Int256; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using static Nethermind.Evm.Tracing.GethStyle.Custom.JavaScript.Log; + +namespace Nethermind.Evm.CodeAnalysis.IL; +internal ref struct ILEvmState +{ + public ReadOnlyMemory MachineCode; + public EvmState EvmState; + // static arguments + // * vmState.Env : + public ref readonly ExecutionEnvironment Env; + // * vmState.Env.TxCtx : + public ref readonly TxExecutionContext TxCtx; + // * vmState.Env.TxCtx.BlkCtx : + public ref readonly BlockExecutionContext BlkCtx; + // in case of exceptions + public EvmExceptionType EvmException; + // in case of jumps crossing section boundaries + public ushort ProgramCounter; + public long GasAvailable; + // in case STOP is executed + public bool ShouldStop; + public bool ShouldRevert; + public bool ShouldReturn; + public bool ShouldJump; + + // * vmState.DataStackHead : + public ref int StackHead; + // * vmState.DataStack : + public Span Stack; + + // * vmState.Memory : + public ref EvmPooledMemory Memory; + + public ref readonly ReadOnlyMemory InputBuffer; + public ref ReadOnlyMemory ReturnBuffer; + + public ILEvmState(EvmState evmState, EvmExceptionType evmException, ushort programCounter, long gasAvailable, ref ReadOnlyMemory returnBuffer) + { + // locals for ease of access + EvmState = evmState; + MachineCode = evmState.Env.CodeInfo.MachineCode; + Env = ref evmState.Env; + TxCtx = ref evmState.Env.TxExecutionContext; + BlkCtx = ref evmState.Env.TxExecutionContext.BlockExecutionContext; + StackHead = ref evmState.DataStackHead; + Stack = evmState.DataStack; + Memory = ref evmState.Memory; + + EvmException = evmException; + ProgramCounter = programCounter; + GasAvailable = gasAvailable; + + InputBuffer = ref evmState.Env.InputData; + ReturnBuffer = ref returnBuffer; + + ShouldStop = false; + ShouldRevert = false; + ShouldReturn = false; + ShouldJump = false; + } +} diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILExtensions.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILExtensions.cs new file mode 100644 index 00000000000..4dc8a49350a --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILExtensions.cs @@ -0,0 +1,220 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Db; +using Nethermind.Evm.CodeAnalysis.IL; +using Sigil; +using System; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; + +namespace Nethermind.Evm.IL; + +/// +/// Extensions for . +/// +static class EmitExtensions +{ + public static MethodInfo GetAsMethodInfo() + { + MethodInfo method = typeof(Unsafe).GetMethods().First((m) => m.Name == nameof(Unsafe.As) && m.ReturnType.IsByRef); + return method.MakeGenericMethod(typeof(TOriginal), typeof(TResult)); + } + +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + public unsafe static MethodInfo GetCastMethodInfo() where TResult : struct + { + MethodInfo method = typeof(EmitExtensions).GetMethod(nameof(EmitExtensions.ReinterpretCast)); + return method.MakeGenericMethod(typeof(TOriginal), typeof(TResult)); + } + public unsafe static Span ReinterpretCast(Span original) + where TOriginal : struct + where TResult : struct + { + Span result = Span.Empty; + fixed (TOriginal* ptr = original) + { + result = new Span(ptr, original.Length * sizeof(TOriginal) / sizeof(TResult)); + } + return result; + } +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + + public static FieldInfo GetFieldInfo(string name) => GetFieldInfo(typeof(T), name); + public static FieldInfo GetFieldInfo(Type TypeInstance, string name) + { + return TypeInstance.GetField(name); + } + public static MethodInfo GetPropertyInfo(string name, bool getSetter, out PropertyInfo propInfo) + => GetPropertyInfo(typeof(T), name, getSetter, out propInfo); + public static MethodInfo GetPropertyInfo(Type typeInstance, string name, bool getSetter, out PropertyInfo propInfo) + { + propInfo = typeInstance.GetProperty(name); + return getSetter ? propInfo.GetSetMethod() : propInfo.GetGetMethod(); + } + + public static void Print(this Emit il, Local local) + { + if(local.LocalType.IsValueType) + { + il.LoadLocalAddress(local); + il.Call(local.LocalType.GetMethod("ToString", [])); + } + else + { + il.LoadLocal(local); + il.CallVirtual(local.LocalType.GetMethod("ToString", [])); + } + il.Call(typeof(Debug).GetMethod(nameof(Debug.WriteLine), [typeof(string)])); + } + public static void Load(this Emit il, Local local, Local idx) + { + il.LoadLocalAddress(local); + il.LoadLocal(idx); + il.Call(typeof(Span).GetMethod("get_Item")); + } + + public static void Load(this Emit il, Local local, Local idx, FieldInfo wordField) + { + il.LoadLocalAddress(local); + il.LoadLocal(idx); + il.Call(typeof(Span).GetMethod("get_Item")); + il.LoadField(wordField); + } + + public static void CleanWord(this Emit il, Local local, Local idx) + { + il.LoadLocalAddress(local); + il.LoadLocal(idx); + il.Call(typeof(Span).GetMethod("get_Item")); + + il.InitializeObject(typeof(Word)); + } + + /// + /// Advances the stack one word up. + /// + public static void StackPush(this Emit il, Local idx, int count = 1) + { + il.LoadLocal(idx); + il.LoadConstant(count); + il.Add(); + il.StoreLocal(idx); + } + + /// + /// Moves the stack words down. + /// + public static void StackPop(this Emit il, Local idx, int count = 1) + { + il.LoadLocal(idx); + il.LoadConstant(count); + il.Subtract(); + il.StoreLocal(idx); + } + + /// + /// Moves the stack words down. + /// + public static void StackPop(this Emit il, Local local, Local count) + { + il.LoadLocal(local); + il.LoadLocal(count); + il.Subtract(); + il.StoreLocal(local); + } + + public static void WhileBranch(this Emit il, Local cond, Action, Local> action) + { + var start = il.DefineLabel(); + var end = il.DefineLabel(); + + // start of the loop + il.MarkLabel(start); + + // if cond + il.LoadLocal(cond); + il.BranchIfFalse(end); + + // emit body of loop + action(il, cond); + + // jump to start of the loop + il.Branch(start); + + // end of the loop + il.MarkLabel(end); + } + + public static void ForBranch(this Emit il, Local count, Action, Local> action) + { + var start = il.DefineLabel(); + var end = il.DefineLabel(); + + // declare i + var i = il.DeclareLocal(); + + // we initialize i to 0 + il.LoadConstant(0); + il.StoreLocal(i); + + // start of the loop + il.MarkLabel(start); + + // i < count + il.LoadLocal(i); + il.LoadLocal(count); + il.BranchIfEqual(end); + + // emit body of loop + action(il, i); + + // i++ + il.LoadLocal(i); + il.LoadConstant(1); + il.Add(); + il.StoreLocal(i); + + // jump to start of the loop + il.Branch(start); + + // end of the loop + il.MarkLabel(end); + } + + /// + /// Loads the previous EVM stack value on top of .NET stack. + /// + public static void StackLoadPrevious(this Emit il, Local src, Local idx, int count = 1) + { + il.LoadLocalAddress(src); + il.LoadLocal(idx); + il.LoadConstant(count); + il.Convert(); + il.Subtract(); + il.Call(typeof(Span).GetMethod("get_Item")); + } + + public static void LoadArray(this Emit il, ReadOnlySpan value) + { + il.LoadConstant(value.Length); + il.NewArray(); + + // get methodInfo of AsSpan from int[] it is a public instance method + + for (int i = 0; i < value.Length; i++) + { + il.Duplicate(); + il.LoadConstant(i); + il.LoadConstant(value[i]); + il.StoreElement(); + } + + il.Call(typeof(ReadOnlySpan).GetMethod("op_Implicit", new[] { typeof(byte[]) })); + + } +} diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlAnalyzer.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlAnalyzer.cs new file mode 100644 index 00000000000..2d889c2e48f --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlAnalyzer.cs @@ -0,0 +1,159 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Int256; +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using static Nethermind.Evm.CodeAnalysis.IL.ILCompiler; +using static System.Net.Mime.MediaTypeNames; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Nethermind.Evm.CodeAnalysis.IL; + +/// +/// Provides +/// +internal static class IlAnalyzer +{ + public class ByteArrayComparer : IEqualityComparer + { + public bool Equals(byte[] left, byte[] right) + { + if (left == null || right == null) + { + return left == right; + } + return left.SequenceEqual(right); + } + public int GetHashCode(byte[] key) + { + if (key == null) + throw new ArgumentNullException("key"); + return key.Sum(b => b); + } + } + + private static Dictionary Patterns = new Dictionary(new ByteArrayComparer()); + public static Dictionary AddPattern(byte[] pattern, InstructionChunk chunk) + { + lock (Patterns) + { + Patterns[pattern] = chunk; + } + return Patterns; + } + public static T GetPatternHandler(byte[] pattern) where T : InstructionChunk + { + return (T)Patterns[pattern]; + } + + + /// + /// Starts the analyzing in a background task and outputs the value in the . + /// thou + /// The destination output. + public static Task StartAnalysis(CodeInfo codeInfo, IlInfo.ILMode mode) + { + return Task.Run(() => Analysis(codeInfo, mode)); + } + + public static (OpcodeInfo[], byte[][]) StripByteCode(ReadOnlySpan machineCode) + { + OpcodeInfo[] opcodes = new OpcodeInfo[machineCode.Length]; + List data = new List(); + int j = 0; + for (ushort i = 0; i < machineCode.Length; i++, j++) + { + Instruction opcode = (Instruction)machineCode[i]; + int? argsIndex = null; + ushort pc = i; + if (opcode is > Instruction.PUSH0 and <= Instruction.PUSH32) + { + ushort immediatesCount = opcode - Instruction.PUSH0; + data.Add(machineCode.SliceWithZeroPadding((UInt256)i + 1, immediatesCount, PadDirection.Left).ToArray()); + argsIndex = data.Count - 1; + i += immediatesCount; + } + opcodes[j] = new OpcodeInfo(pc, opcode, argsIndex); + } + return (opcodes[..j], data.ToArray()); + } + + /// + /// For now, return null always to default to EVM. + /// + private static void Analysis(CodeInfo codeInfo, IlInfo.ILMode mode) + { + ReadOnlyMemory machineCode = codeInfo.MachineCode; + + FrozenDictionary SegmentCode((OpcodeInfo[], byte[][]) codeData) + { + Dictionary opcodeInfos = []; + + List segment = []; + foreach (var opcode in codeData.Item1) + { + if (opcode.Operation.IsStateful()) + { + if (segment.Count > 0) + { + opcodeInfos.Add(segment[0].ProgramCounter, ILCompiler.CompileSegment($"ILEVM_{Guid.NewGuid()}", segment.ToArray(), codeData.Item2)); + segment.Clear(); + } + } + else + { + segment.Add(opcode); + } + } + if (segment.Count > 0) + { + opcodeInfos.Add(segment[0].ProgramCounter, ILCompiler.CompileSegment($"ILEVM_{Guid.NewGuid()}", segment.ToArray(), codeData.Item2)); + } + return opcodeInfos.ToFrozenDictionary(); + } + + FrozenDictionary CheckPatterns(ReadOnlyMemory machineCode) + { + var (strippedBytecode, data) = StripByteCode(machineCode.Span); + var patternFound = new Dictionary(); + foreach (var (pattern, mapping) in Patterns) + { + for (int i = 0; i < strippedBytecode.Length - pattern.Length + 1; i++) + { + bool found = true; + for (int j = 0; j < pattern.Length && found; j++) + { + found = ((byte)strippedBytecode[i + j].Operation == pattern[j]); + } + + if (found) + { + patternFound.Add((ushort)i, mapping); + i += pattern.Length - 1; + } + } + } + return patternFound.ToFrozenDictionary(); + } + + switch (mode) + { + case IlInfo.ILMode.PatternMatching: + codeInfo.IlInfo.WithChunks(CheckPatterns(machineCode)); + break; + case IlInfo.ILMode.SubsegmentsCompiling: + codeInfo.IlInfo.WithSegments(SegmentCode(StripByteCode(machineCode.Span))); + break; + } + } + + /// + /// How many execution a should perform before trying to get its opcodes optimized. + /// + public const int CompoundOpThreshold = 23; + public const int IlCompilerThreshold = 57; +} diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlInfo.cs new file mode 100644 index 00000000000..baa2e11cf9f --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlInfo.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections.Frozen; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Microsoft.IdentityModel.Tokens; +using Nethermind.Core; +using Nethermind.Core.Specs; +using static Nethermind.Evm.CodeAnalysis.IL.ILCompiler; + +namespace Nethermind.Evm.CodeAnalysis.IL; +/// +/// Represents the IL-EVM information about the contract. +/// +internal class IlInfo +{ + public enum ILMode + { + NoIlvm = 0, + PatternMatching = 1, + SubsegmentsCompiling = 2 + } + + /// + /// Represents an information about IL-EVM being not able to optimize the given . + /// + public static IlInfo Empty => new(); + + /// + /// Represents what mode of IL-EVM is used. 0 is the default. [0 = Pattern matching, 1 = subsegments compiling] + /// + public static readonly ILMode Mode = ILMode.PatternMatching; + public bool IsEmpty => Chunks.Count == 0 && Segments.Count == 0; + /// + /// No overrides. + /// + private IlInfo() + { + Chunks = FrozenDictionary.Empty; + Segments = FrozenDictionary.Empty; + } + + public IlInfo WithChunks(FrozenDictionary chunks) + { + Chunks = chunks; + return this; + } + + public IlInfo WithSegments(FrozenDictionary segments) + { + Segments = segments; + return this; + } + + public IlInfo(FrozenDictionary mappedOpcodes, FrozenDictionary segments) + { + Chunks = mappedOpcodes; + Segments = segments; + } + + // assumes small number of ILed + public FrozenDictionary Chunks { get; set; } + public FrozenDictionary Segments { get; set; } + + public bool TryExecute(EvmState vmState, ref ReadOnlyMemory outputBuffer, ISpecProvider specProvider, IBlockhashProvider blockHashProvider, ref int programCounter, ref long gasAvailable, ref EvmStack stack, out bool shouldJump, out bool shouldStop, out bool shouldRevert, out bool shouldReturn, out object returnData) + where TTracingInstructions : struct, VirtualMachine.IIsTracing + { + shouldReturn = false; + shouldRevert = false; + shouldStop = false; + shouldJump = false; + returnData = null; + if (programCounter > ushort.MaxValue) + return false; + + switch (Mode) + { + case ILMode.PatternMatching: + { + if (Chunks.TryGetValue((ushort)programCounter, out InstructionChunk chunk) == false) + { + return false; + } + var blkCtx = vmState.Env.TxExecutionContext.BlockExecutionContext; + chunk.Invoke(vmState, specProvider.GetSpec(blkCtx.Header.Number, blkCtx.Header.Timestamp), ref programCounter, ref gasAvailable, ref stack); + break; + } + case ILMode.SubsegmentsCompiling: + { + if (Segments.TryGetValue((ushort)programCounter, out SegmentExecutionCtx ctx) == false) + { + return false; + } + + var ilvmState = new ILEvmState(vmState, EvmExceptionType.None, (ushort)programCounter, gasAvailable, ref outputBuffer); + + ctx.Method.Invoke(ref ilvmState, specProvider, blockHashProvider, ctx.Data); + + gasAvailable = ilvmState.GasAvailable; + programCounter = ilvmState.ProgramCounter; + shouldStop = ilvmState.ShouldStop; + shouldReturn = ilvmState.ShouldReturn; + shouldRevert = ilvmState.ShouldRevert; + + returnData = ilvmState.ReturnBuffer; + shouldJump = ilvmState.ShouldJump; + + break; + } + } + return true; + } +} diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/InstructionChunk.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/InstructionChunk.cs new file mode 100644 index 00000000000..a180b22e5c4 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/InstructionChunk.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Specs; +using static Nethermind.Evm.VirtualMachine; + +namespace Nethermind.Evm.CodeAnalysis.IL; + +/// +/// Represents a chunk of s that is optimized and ready to be run in an efficient manner. +/// +/// +interface InstructionChunk +{ + static byte[] Pattern { get; } + void Invoke(EvmState vmState, IReleaseSpec spec, ref int programCounter, + ref long gasAvailable, + ref EvmStack stack) where T : struct, IIsTracing; +} diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/Word.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/Word.cs new file mode 100644 index 00000000000..b4428dcea78 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/Word.cs @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; +using Nethermind.Trie; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Evm.CodeAnalysis.IL; + +[StructLayout(LayoutKind.Explicit, Size = 32)] +internal struct Word +{ + public const int Size = 32; + + [FieldOffset(0)] public unsafe fixed byte _buffer[Size]; + + [FieldOffset(Size - sizeof(byte))] + public byte Byte0; + + [FieldOffset(Size - sizeof(int))] + public int Int0; + + [FieldOffset(Size - sizeof(int))] + public uint UInt0; + + [FieldOffset(Size - 2 * sizeof(int))] + public uint UInt1; + + [FieldOffset(Size - 1 * sizeof(ulong))] + public ulong Ulong0; + + [FieldOffset(Size - 2 * sizeof(ulong))] + public ulong Ulong1; + + [FieldOffset(Size - 3 * sizeof(ulong))] + public ulong Ulong2; + + [FieldOffset(Size - 4 * sizeof(ulong))] + public ulong Ulong3; + + public bool IsZero => (Ulong0 | Ulong1 | Ulong2 | Ulong3) == 0; + public void ToZero() + { + Ulong0 = 0; Ulong1 = 0; + Ulong2 = 0; Ulong3 = 0; + } + + public unsafe byte[] Array + { + get + { + byte[] array = new byte[32]; + fixed (byte* src = _buffer, dest = array) + { + Buffer.MemoryCopy(src, dest, 32, 32); + } + return array; + } + set + { + fixed (byte* src = value, dest = _buffer) + { + Buffer.MemoryCopy(src, dest + (32 - value.Length), value.Length, value.Length); + } + } + } + + public unsafe ReadOnlySpan Span + { + get + { + fixed (byte* src = _buffer) + { + return new Span(src, 32); + } + } + set + { + fixed (byte* src = value, dest = _buffer) + { + Buffer.MemoryCopy(src, dest + (32 - value.Length), value.Length, value.Length); + } + } + } + + public unsafe ValueHash256 Keccak + { + get + { + fixed (byte* ptr = _buffer) + { + return new ValueHash256(new Span(ptr, 32)); + } + } + set + { + ReadOnlySpan buffer = value.Bytes; + for (int i = 0; i < 20; i++) + { + _buffer[i] = buffer[i]; + } + } + } + + public unsafe Address Address + { + get + { + byte[] buffer = new byte[20]; + for (int i = 0; i < 20; i++) + { + buffer[i] = _buffer[i]; + } + + return new Address(buffer); + } + set + { + byte[] buffer = value.Bytes; + for (int i = 0; i < 20; i++) + { + _buffer[i] = buffer[i]; + } + } + } + + public UInt256 UInt256 + { + get + { + ulong u3 = Ulong3; + ulong u2 = Ulong2; + ulong u1 = Ulong1; + ulong u0 = Ulong0; + + if (BitConverter.IsLittleEndian) + { + u3 = BinaryPrimitives.ReverseEndianness(u3); + u2 = BinaryPrimitives.ReverseEndianness(u2); + u1 = BinaryPrimitives.ReverseEndianness(u1); + u0 = BinaryPrimitives.ReverseEndianness(u0); + } + + return new UInt256(u0, u1, u2, u3); + } + set + { + if (BitConverter.IsLittleEndian) + { + Ulong3 = BinaryPrimitives.ReverseEndianness(value.u3); + Ulong2 = BinaryPrimitives.ReverseEndianness(value.u2); + Ulong1 = BinaryPrimitives.ReverseEndianness(value.u1); + Ulong0 = BinaryPrimitives.ReverseEndianness(value.u0); + } + else + { + Ulong3 = value.u3; + Ulong2 = value.u2; + Ulong1 = value.u1; + Ulong0 = value.u0; + } + } + } + + public static readonly FieldInfo Byte0Field = typeof(Word).GetField(nameof(Byte0)); + + public static readonly FieldInfo Int0Field = typeof(Word).GetField(nameof(Int0)); + + public static readonly FieldInfo UInt0Field = typeof(Word).GetField(nameof(UInt0)); + public static readonly FieldInfo UInt1Field = typeof(Word).GetField(nameof(UInt1)); + + public static readonly FieldInfo Ulong0Field = typeof(Word).GetField(nameof(Ulong0)); + public static readonly FieldInfo Ulong1Field = typeof(Word).GetField(nameof(Ulong1)); + public static readonly FieldInfo Ulong2Field = typeof(Word).GetField(nameof(Ulong2)); + public static readonly FieldInfo Ulong3Field = typeof(Word).GetField(nameof(Ulong3)); + + public static readonly MethodInfo GetIsZero = typeof(Word).GetProperty(nameof(IsZero))!.GetMethod; + public static readonly MethodInfo SetToZero = typeof(Word).GetMethod(nameof(ToZero))!; + + public static readonly MethodInfo GetUInt256 = typeof(Word).GetProperty(nameof(UInt256))!.GetMethod; + public static readonly MethodInfo SetUInt256 = typeof(Word).GetProperty(nameof(UInt256))!.SetMethod; + + public static readonly MethodInfo GetAddress = typeof(Word).GetProperty(nameof(Address))!.GetMethod; + public static readonly MethodInfo SetAddress = typeof(Word).GetProperty(nameof(Address))!.SetMethod; + + public static readonly MethodInfo GetKeccak = typeof(Word).GetProperty(nameof(Keccak))!.GetMethod; + public static readonly MethodInfo SetKeccak = typeof(Word).GetProperty(nameof(Keccak))!.SetMethod; + + public static readonly MethodInfo GetArray = typeof(Word).GetProperty(nameof(Array))!.GetMethod; + public static readonly MethodInfo SetArray = typeof(Word).GetProperty(nameof(Array))!.SetMethod; + + public static readonly MethodInfo GetSpan = typeof(Word).GetProperty(nameof(Span))!.GetMethod; + public static readonly MethodInfo SetSpan = typeof(Word).GetProperty(nameof(Span))!.SetMethod; +} diff --git a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs index ec6600fef8e..15c57382a0d 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs @@ -58,6 +58,7 @@ public ExecutionEnvironment /// public readonly ReadOnlyMemory InputData; + /// /// Transaction originator /// diff --git a/src/Nethermind/Nethermind.Evm/Instruction.cs b/src/Nethermind/Nethermind.Evm/Instruction.cs index 96fcf3f3595..0d43c1940b0 100644 --- a/src/Nethermind/Nethermind.Evm/Instruction.cs +++ b/src/Nethermind/Nethermind.Evm/Instruction.cs @@ -1,7 +1,11 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Collections.Frozen; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using FastEnumUtility; using Nethermind.Core.Specs; @@ -172,9 +176,222 @@ public enum Instruction : byte INVALID = 0xfe, SELFDESTRUCT = 0xff, } + public struct OpcodeMetadata(long gasCost, byte additionalBytes, byte stackBehaviorPop, byte stackBehaviorPush) + { + /// + /// The gas cost. + /// + public long GasCost { get; } = gasCost; + + /// + /// How many following bytes does this instruction have. + /// + public byte AdditionalBytes { get; } = additionalBytes; + + /// + /// How many bytes are popped by this instruction. + /// + public byte StackBehaviorPop { get; } = stackBehaviorPop; + + /// + /// How many bytes are pushed by this instruction. + /// + public byte StackBehaviorPush { get; } = stackBehaviorPush; + + public static readonly IReadOnlyDictionary Operations = + new Dictionary() + { + [Instruction.POP] = new(GasCostOf.Base, 0, 1, 0), + [Instruction.STOP] = new(0, 0, 0, 0), + [Instruction.PC] = new(GasCostOf.Base, 0, 0, 1), + + [Instruction.PUSH1] = new(GasCostOf.VeryLow, 1, 0, 1), + [Instruction.PUSH2] = new(GasCostOf.VeryLow, 2, 0, 1), + [Instruction.PUSH3] = new(GasCostOf.VeryLow, 3, 0, 1), + [Instruction.PUSH4] = new(GasCostOf.VeryLow, 4, 0, 1), + [Instruction.PUSH5] = new(GasCostOf.VeryLow, 5, 0, 1), + [Instruction.PUSH6] = new(GasCostOf.VeryLow, 6, 0, 1), + [Instruction.PUSH7] = new(GasCostOf.VeryLow, 7, 0, 1), + [Instruction.PUSH8] = new(GasCostOf.VeryLow, 8, 0, 1), + [Instruction.PUSH9] = new(GasCostOf.VeryLow, 9, 0, 1), + [Instruction.PUSH10] = new(GasCostOf.VeryLow, 10, 0, 1), + [Instruction.PUSH11] = new(GasCostOf.VeryLow, 11, 0, 1), + [Instruction.PUSH12] = new(GasCostOf.VeryLow, 12, 0, 1), + [Instruction.PUSH13] = new(GasCostOf.VeryLow, 13, 0, 1), + [Instruction.PUSH14] = new(GasCostOf.VeryLow, 14, 0, 1), + [Instruction.PUSH15] = new(GasCostOf.VeryLow, 15, 0, 1), + [Instruction.PUSH16] = new(GasCostOf.VeryLow, 16, 0, 1), + [Instruction.PUSH17] = new(GasCostOf.VeryLow, 17, 0, 1), + [Instruction.PUSH18] = new(GasCostOf.VeryLow, 18, 0, 1), + [Instruction.PUSH19] = new(GasCostOf.VeryLow, 19, 0, 1), + [Instruction.PUSH20] = new(GasCostOf.VeryLow, 20, 0, 1), + [Instruction.PUSH21] = new(GasCostOf.VeryLow, 21, 0, 1), + [Instruction.PUSH22] = new(GasCostOf.VeryLow, 22, 0, 1), + [Instruction.PUSH23] = new(GasCostOf.VeryLow, 23, 0, 1), + [Instruction.PUSH24] = new(GasCostOf.VeryLow, 24, 0, 1), + [Instruction.PUSH25] = new(GasCostOf.VeryLow, 25, 0, 1), + [Instruction.PUSH26] = new(GasCostOf.VeryLow, 26, 0, 1), + [Instruction.PUSH27] = new(GasCostOf.VeryLow, 27, 0, 1), + [Instruction.PUSH28] = new(GasCostOf.VeryLow, 28, 0, 1), + [Instruction.PUSH29] = new(GasCostOf.VeryLow, 29, 0, 1), + [Instruction.PUSH30] = new(GasCostOf.VeryLow, 30, 0, 1), + [Instruction.PUSH31] = new(GasCostOf.VeryLow, 31, 0, 1), + [Instruction.PUSH32] = new(GasCostOf.VeryLow, 32, 0, 1), + + [Instruction.JUMPDEST] = new(GasCostOf.JumpDest, 0, 0, 0), + [Instruction.JUMP] = new(GasCostOf.Mid, 0, 1, 0), + [Instruction.JUMPI] = new(GasCostOf.High, 0, 2, 0), + [Instruction.SUB] = new(GasCostOf.VeryLow, 0, 2, 1), + + [Instruction.DUP1] = new(GasCostOf.VeryLow, 0, 1, 2), + [Instruction.DUP2] = new(GasCostOf.VeryLow, 0, 2, 3), + [Instruction.DUP3] = new(GasCostOf.VeryLow, 0, 3, 4), + [Instruction.DUP4] = new(GasCostOf.VeryLow, 0, 4, 5), + [Instruction.DUP5] = new(GasCostOf.VeryLow, 0, 5, 6), + [Instruction.DUP6] = new(GasCostOf.VeryLow, 0, 6, 7), + [Instruction.DUP7] = new(GasCostOf.VeryLow, 0, 7, 8), + [Instruction.DUP8] = new(GasCostOf.VeryLow, 0, 8, 9), + [Instruction.DUP9] = new(GasCostOf.VeryLow, 0, 9, 10), + [Instruction.DUP10] = new(GasCostOf.VeryLow, 0, 10, 11), + [Instruction.DUP11] = new(GasCostOf.VeryLow, 0, 11, 12), + [Instruction.DUP12] = new(GasCostOf.VeryLow, 0, 12, 13), + [Instruction.DUP13] = new(GasCostOf.VeryLow, 0, 13, 14), + [Instruction.DUP14] = new(GasCostOf.VeryLow, 0, 14, 15), + [Instruction.DUP15] = new(GasCostOf.VeryLow, 0, 15, 16), + [Instruction.DUP16] = new(GasCostOf.VeryLow, 0, 16, 17), + + [Instruction.SWAP1] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP2] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP3] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP4] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP5] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP6] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP7] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP8] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP9] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP10] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP11] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP12] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP13] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP14] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP15] = new(GasCostOf.VeryLow, 0, 2, 2), + [Instruction.SWAP16] = new(GasCostOf.VeryLow, 0, 2, 2), + + [Instruction.ADD] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.MUL] = new(GasCostOf.Low, 0, 2, 1), + [Instruction.SUB] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.DIV] = new(GasCostOf.Low, 0, 2, 1), + [Instruction.SDIV] = new(GasCostOf.Low, 0, 2, 1), + [Instruction.MOD] = new(GasCostOf.Low, 0, 2, 1), + [Instruction.SMOD] = new(GasCostOf.Low, 0, 2, 1), + [Instruction.EXP] = new(GasCostOf.Exp, 0, 2, 1), + [Instruction.SIGNEXTEND] = new(GasCostOf.Low, 0, 2, 1), + [Instruction.LT] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.GT] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.SLT] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.SGT] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.EQ] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.ISZERO] = new(GasCostOf.VeryLow, 0, 1, 1), + [Instruction.AND] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.OR] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.XOR] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.NOT] = new(GasCostOf.VeryLow, 0, 1, 1), + [Instruction.ADDMOD] = new(GasCostOf.Mid, 0, 3, 1), + [Instruction.MULMOD] = new(GasCostOf.Mid, 0, 3, 1), + [Instruction.SHL] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.SHR] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.SAR] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.BYTE] = new(GasCostOf.VeryLow, 0, 2, 1), + + [Instruction.KECCAK256] = new(GasCostOf.VeryLow, 0, 2, 1), + [Instruction.ADDRESS] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.BALANCE] = new(GasCostOf.Balance, 0, 1, 1), + [Instruction.ORIGIN] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.CALLER] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.CALLVALUE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.CALLDATALOAD] = new(GasCostOf.VeryLow, 0, 1, 1), + [Instruction.CALLDATASIZE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.CALLDATACOPY] = new(GasCostOf.VeryLow, 0, 3, 0), + [Instruction.CODESIZE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.CODECOPY] = new(GasCostOf.VeryLow, 0, 3, 0), + [Instruction.GASPRICE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.EXTCODESIZE] = new(GasCostOf.ExtCode, 0, 1, 1), + [Instruction.EXTCODECOPY] = new(GasCostOf.ExtCode, 0, 4, 0), + [Instruction.RETURNDATASIZE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.RETURNDATACOPY] = new(GasCostOf.VeryLow, 0, 3, 0), + [Instruction.EXTCODEHASH] = new(GasCostOf.ExtCodeHash, 0, 1, 1), + [Instruction.EXTCODECOPY] = new(GasCostOf.ExtCode, 0, 4, 0), + + [Instruction.BLOCKHASH] = new(GasCostOf.BlockHash, 0, 1, 1), + [Instruction.COINBASE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.TIMESTAMP] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.NUMBER] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.PREVRANDAO] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.GASLIMIT] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.CHAINID] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.SELFBALANCE] = new(GasCostOf.SelfBalance, 0, 0, 1), + [Instruction.BASEFEE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.BLOBHASH] = new(GasCostOf.BlobHash, 0, 1, 1), + [Instruction.BLOBBASEFEE] = new(GasCostOf.Base, 0, 0, 1), + + [Instruction.POP] = new(GasCostOf.Base, 0, 1, 0), + [Instruction.MLOAD] = new(GasCostOf.VeryLow, 0, 1, 1), + [Instruction.MSTORE] = new(GasCostOf.VeryLow, 0, 2, 0), + [Instruction.MSTORE8] = new(GasCostOf.VeryLow, 0, 2, 0), + [Instruction.SLOAD] = new(GasCostOf.SLoad, 0, 1, 1), + [Instruction.SSTORE] = new(GasCostOf.SSet, 0, 2, 0), + [Instruction.JUMP] = new(GasCostOf.Mid, 0, 1, 0), + [Instruction.JUMPI] = new(GasCostOf.Mid, 0, 2, 0), + [Instruction.PC] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.MSIZE] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.GAS] = new(GasCostOf.Base, 0, 0, 1), + [Instruction.JUMPDEST] = new(GasCostOf.JumpDest, 0, 0, 0), + [Instruction.MCOPY] = new(GasCostOf.VeryLow, 0, 3, 0), + + [Instruction.LOG0] = new(GasCostOf.Log, 0, 2, 0), + [Instruction.LOG1] = new(GasCostOf.Log, 0, 3, 0), + [Instruction.LOG2] = new(GasCostOf.Log, 0, 4, 0), + [Instruction.LOG3] = new(GasCostOf.Log, 0, 5, 0), + [Instruction.LOG4] = new(GasCostOf.Log, 0, 6, 0), + + [Instruction.TLOAD] = new(GasCostOf.Base, 0, 1, 1), + [Instruction.TSTORE] = new(GasCostOf.Base, 0, 2, 0), + + [Instruction.CREATE] = new(GasCostOf.Create, 0, 3, 1), + [Instruction.CALL] = new(GasCostOf.Call, 0, 7, 1), + [Instruction.CALLCODE] = new(GasCostOf.Call, 0, 7, 1), + [Instruction.RETURN] = new(GasCostOf.Base, 0, 2, 0), + [Instruction.DELEGATECALL] = new(GasCostOf.Call, 0, 6, 1), + [Instruction.CREATE2] = new(GasCostOf.Create, 0, 4, 1), + [Instruction.STATICCALL] = new(GasCostOf.Call, 0, 6, 1), + [Instruction.REVERT] = new(GasCostOf.Base, 0, 2, 0), + [Instruction.INVALID] = new(GasCostOf.Base, 0, 0, 0), + [Instruction.SELFDESTRUCT] = new(GasCostOf.SelfDestruct, 0, 1, 0), + }.ToFrozenDictionary(); + } + public struct OpcodeInfo(ushort pc, Instruction instruction, int? argumentIndex) + { + public OpcodeMetadata? Metadata => OpcodeMetadata.Operations[instruction]; + public Instruction Operation => instruction; + public ushort ProgramCounter => pc; + public int? Arguments { get; set; } = argumentIndex; + } public static class InstructionExtensions { + public static bool IsStateful(this Instruction instruction) => instruction switch + { + Instruction.CREATE or Instruction.CREATE2 => true, + Instruction.CALL or Instruction.CALLCODE or Instruction.DELEGATECALL or Instruction.STATICCALL => true, + Instruction.SLOAD or Instruction.SSTORE => true, + Instruction.TLOAD or Instruction.TSTORE => true, + Instruction.EXTCODESIZE or Instruction.EXTCODECOPY or Instruction.EXTCODEHASH => true, + Instruction.SELFDESTRUCT => true, + Instruction.BALANCE => true, + Instruction.SELFBALANCE => true, + _ => false, + }; + public static string? GetName(this Instruction instruction, bool isPostMerge = false, IReleaseSpec? spec = null) => instruction switch { diff --git a/src/Nethermind/Nethermind.Evm/Nethermind.Evm.csproj b/src/Nethermind/Nethermind.Evm/Nethermind.Evm.csproj index 8f6c31d198f..f747561d4b7 100644 --- a/src/Nethermind/Nethermind.Evm/Nethermind.Evm.csproj +++ b/src/Nethermind/Nethermind.Evm/Nethermind.Evm.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 01e891b07f5..d5d4b768abd 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -17,6 +17,7 @@ using Nethermind.State; using System.Diagnostics.CodeAnalysis; using System.Runtime.Intrinsics; +using Nethermind.Evm.CodeAnalysis.IL; using static Nethermind.Evm.VirtualMachine; using static System.Runtime.CompilerServices.Unsafe; @@ -40,7 +41,7 @@ public class VirtualMachine : IVirtualMachine internal static readonly byte[] BytesZero = [0]; - internal static readonly byte[] BytesZero32 = + public static readonly byte[] BytesZero32 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -48,7 +49,7 @@ public class VirtualMachine : IVirtualMachine 0, 0, 0, 0, 0, 0, 0, 0 }; - internal static readonly byte[] BytesMax32 = + public static readonly byte[] BytesMax32 = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, @@ -639,6 +640,8 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM goto Empty; } + vmState.Env.CodeInfo.NoticeExecution(); + vmState.InitStacks(); EvmStack stack = new(vmState.DataStack.AsSpan(), vmState.DataStackHead, _txTracer); long gasAvailable = vmState.GasAvailable; @@ -652,7 +655,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (previousCallOutput.Length > 0) { UInt256 localPreviousDest = previousCallOutputDestination; - if (!UpdateMemoryCost(vmState, ref gasAvailable, in localPreviousDest, (ulong)previousCallOutput.Length)) + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in localPreviousDest, (ulong)previousCallOutput.Length)) { goto OutOfGas; } @@ -695,6 +698,12 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM ref readonly BlockExecutionContext blkCtx = ref txCtx.BlockExecutionContext; ReadOnlySpan code = env.CodeInfo.MachineCode.Span; EvmExceptionType exceptionType = EvmExceptionType.None; + IlInfo? ilInfo = (typeof(TTracingInstructions) == typeof(NotTracing) && + typeof(TTracingRefunds) == typeof(NotTracing) && + typeof(TTracingStorage) == typeof(NotTracing)) + ? env.CodeInfo.IlInfo + : null; + bool isRevert = false; #if DEBUG DebugTracer? debugger = _txTracer.GetTracer(); @@ -704,15 +713,29 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM SkipInit(out UInt256 c); SkipInit(out UInt256 result); SkipInit(out StorageCell storageCell); - object returnData; + object returnData = null; + ZeroPaddedSpan slice; bool isCancelable = _txTracer.IsCancelable; uint codeLength = (uint)code.Length; + bool shouldStop = false; + bool shouldReturn = false; + bool shouldJump = false; while ((uint)programCounter < codeLength) { #if DEBUG debugger?.TryWait(ref vmState, ref programCounter, ref gasAvailable, ref stack.Head); #endif + + // try execute as many as possible + while ((ilInfo?.TryExecute(vmState, ref _returnDataBuffer, _specProvider, _blockhashProvider, ref programCounter, ref gasAvailable, ref stack, out shouldJump, out shouldStop, out shouldReturn, out isRevert, out returnData)) + .GetValueOrDefault(false)) + { + if (shouldReturn || isRevert) goto DataReturn; + if (shouldStop is true) goto EmptyReturn; + if (shouldJump && !vmState.Env.CodeInfo.ValidateJump(programCounter)) goto InvalidJumpDestination; + } + Instruction instruction = (Instruction)code[programCounter]; if (isCancelable && _txTracer.IsCancelled) @@ -1129,7 +1152,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!stack.PopUInt256(out b)) goto StackUnderflow; gasAvailable -= GasCostOf.Sha3 + GasCostOf.Sha3Word * EvmPooledMemory.Div32Ceiling(in b); - if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, b)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in a, b)) goto OutOfGas; bytes = vmState.Memory.LoadSpan(in a, b); stack.PushBytes(ValueKeccak.Compute(bytes).BytesAsSpan); @@ -1202,7 +1225,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!result.IsZero) { - if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, in result)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in a, in result)) goto OutOfGas; slice = env.InputData.SliceWithZeroPadding(b, (int)result); vmState.Memory.Save(in a, in slice); @@ -1231,7 +1254,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!result.IsZero) { - if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, result)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in a, result)) goto OutOfGas; slice = code.SliceWithZeroPadding(in b, (int)result); vmState.Memory.Save(in a, in slice); @@ -1323,7 +1346,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!result.IsZero) { - if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, result)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in a, result)) goto OutOfGas; ReadOnlyMemory externalCode = _codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; slice = externalCode.SliceWithZeroPadding(b, (int)result); @@ -1362,7 +1385,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!result.IsZero) { - if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, result)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in a, result)) goto OutOfGas; slice = _returnDataBuffer.Span.SliceWithZeroPadding(b, (int)result); vmState.Memory.Save(in a, in slice); @@ -1512,7 +1535,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM gasAvailable -= GasCostOf.VeryLow; if (!stack.PopUInt256(out result)) goto StackUnderflow; - if (!UpdateMemoryCost(vmState, ref gasAvailable, in result, in BigInt32)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in result, in BigInt32)) goto OutOfGas; bytes = vmState.Memory.LoadSpan(in result); if (typeof(TTracingInstructions) == typeof(IsTracing)) _txTracer.ReportMemoryChange(result, bytes); @@ -1526,7 +1549,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!stack.PopUInt256(out result)) goto StackUnderflow; bytes = stack.PopWord256(); - if (!UpdateMemoryCost(vmState, ref gasAvailable, in result, in BigInt32)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in result, in BigInt32)) goto OutOfGas; vmState.Memory.SaveWord(in result, bytes); if (typeof(TTracingInstructions) == typeof(IsTracing)) _txTracer.ReportMemoryChange((long)result, bytes); @@ -1538,7 +1561,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!stack.PopUInt256(out result)) goto StackUnderflow; byte data = stack.PopByte(); - if (!UpdateMemoryCost(vmState, ref gasAvailable, in result, UInt256.One)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in result, UInt256.One)) goto OutOfGas; vmState.Memory.SaveByte(in result, data); if (typeof(TTracingInstructions) == typeof(IsTracing)) _txTracer.ReportMemoryChange((long)result, data); @@ -1952,7 +1975,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (!stack.PopUInt256(out c)) goto StackUnderflow; gasAvailable -= GasCostOf.VeryLow + GasCostOf.VeryLow * EvmPooledMemory.Div32Ceiling(c); - if (!UpdateMemoryCost(vmState, ref gasAvailable, UInt256.Max(b, a), c)) goto OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, UInt256.Max(b, a), c)) goto OutOfGas; bytes = vmState.Memory.LoadSpan(in b, c); if (typeof(TTracingInstructions) == typeof(IsTracing)) _txTracer.ReportMemoryChange(b, bytes); @@ -2109,8 +2132,8 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM } if (!UpdateGas(spec.GetCallCost(), ref gasAvailable) || - !UpdateMemoryCost(vmState, ref gasAvailable, in dataOffset, dataLength) || - !UpdateMemoryCost(vmState, ref gasAvailable, in outputOffset, outputLength) || + !UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in dataOffset, dataLength) || + !UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in outputOffset, outputLength) || !UpdateGas(gasExtra, ref gasAvailable)) return EvmExceptionType.OutOfGas; CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); @@ -2240,7 +2263,7 @@ private static EvmExceptionType InstructionRevert(EvmState vmState, re !stack.PopUInt256(out UInt256 length)) return EvmExceptionType.StackUnderflow; - if (!UpdateMemoryCost(vmState, ref gasAvailable, in position, in length)) + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in position, in length)) { return EvmExceptionType.OutOfGas; } @@ -2259,7 +2282,7 @@ private static EvmExceptionType InstructionReturn(EvmState vmState, re !stack.PopUInt256(out UInt256 length)) return EvmExceptionType.StackUnderflow; - if (!UpdateMemoryCost(vmState, ref gasAvailable, in position, in length)) + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in position, in length)) { return EvmExceptionType.OutOfGas; } @@ -2350,7 +2373,7 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref if (!UpdateGas(gasCost, ref gasAvailable)) return (EvmExceptionType.OutOfGas, null); - if (!UpdateMemoryCost(vmState, ref gasAvailable, in memoryPositionOfInitCode, initCodeLength)) return (EvmExceptionType.OutOfGas, null); + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in memoryPositionOfInitCode, initCodeLength)) return (EvmExceptionType.OutOfGas, null); // TODO: copy pasted from CALL / DELEGATECALL, need to move it outside? if (env.CallDepth >= MaxCallDepth) // TODO: fragile ordering / potential vulnerability for different clients @@ -2459,7 +2482,7 @@ private EvmExceptionType InstructionLog(EvmState vmState, ref EvmStack if (!stack.PopUInt256(out UInt256 position)) return EvmExceptionType.StackUnderflow; if (!stack.PopUInt256(out UInt256 length)) return EvmExceptionType.StackUnderflow; long topicsCount = instruction - Instruction.LOG0; - if (!UpdateMemoryCost(vmState, ref gasAvailable, in position, length)) return EvmExceptionType.OutOfGas; + if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in position, length)) return EvmExceptionType.OutOfGas; if (!UpdateGas( GasCostOf.Log + topicsCount * GasCostOf.LogTopic + (long)length * GasCostOf.LogData, ref gasAvailable)) return EvmExceptionType.OutOfGas; @@ -2684,9 +2707,9 @@ private static void UpdateCurrentState(EvmState state, int pc, long gas, int sta state.DataStackHead = stackHead; } - private static bool UpdateMemoryCost(EvmState vmState, ref long gasAvailable, in UInt256 position, in UInt256 length) + public static bool UpdateMemoryCost(ref EvmPooledMemory memory, ref long gasAvailable, in UInt256 position, in UInt256 length) { - long memoryCost = vmState.Memory.CalculateMemoryCost(in position, length); + long memoryCost = memory.CalculateMemoryCost(in position, length); if (memoryCost != 0L) { if (!UpdateGas(memoryCost, ref gasAvailable))