Skip to content

Commit

Permalink
Wait for background JumpDestinationAnalysis if in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams committed May 10, 2024
1 parent 649ca19 commit 0007a71
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 8 deletions.
8 changes: 8 additions & 0 deletions src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,13 @@ void IThreadPoolWorkItem.Execute()
{
_analyzer.Execute();
}

public void AnalyseInBackgroundIfRequired()
{
if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && _analyzer.RequiresAnalysis)
{
ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Threading;

namespace Nethermind.Evm.CodeAnalysis
{
Expand All @@ -19,17 +20,54 @@ public sealed class JumpDestinationAnalyzer(ReadOnlyMemory<byte> code)
private static readonly long[]? _emptyJumpDestinationBitmap = new long[1];
private long[]? _jumpDestinationBitmap = code.Length == 0 ? _emptyJumpDestinationBitmap : null;

private object? _analysisComplete;
private ReadOnlyMemory<byte> MachineCode { get; } = code;

public bool ValidateJump(int destination)
{
ReadOnlySpan<byte> machineCode = MachineCode.Span;
_jumpDestinationBitmap ??= CreateJumpDestinationBitmap(machineCode);
_jumpDestinationBitmap ??= CreateOrWaitForJumpDestinationBitmap();

// Cast to uint to change negative numbers to very int high numbers
// Then do length check, this both reduces check by 1 and eliminates the bounds
// check from accessing the span.
return (uint)destination < (uint)machineCode.Length && IsJumpDestination(_jumpDestinationBitmap, destination);
return (uint)destination < (uint)MachineCode.Length && IsJumpDestination(_jumpDestinationBitmap, destination);
}

private long[] CreateOrWaitForJumpDestinationBitmap()
{
object? previous = Volatile.Read(ref _analysisComplete);
if (previous is null)
{
ManualResetEventSlim analysisComplete = new(initialState: false);
previous = Interlocked.CompareExchange(ref _analysisComplete, analysisComplete, null);
if (previous is null)
{
// Not already in progress, so start it.
var bitmap = CreateJumpDestinationBitmap();
_jumpDestinationBitmap = bitmap;
// Release the MRES to be GC'd
_analysisComplete = bitmap;
// Signal complete.
analysisComplete.Set();
return bitmap;
}
}
else if (previous is ManualResetEventSlim resetEvent)
{
Thread thread = Thread.CurrentThread;
ThreadPriority priority = thread.Priority;
// We are waiting, so drop priority to normal (BlockProcessing runs at higher priority).
thread.Priority = ThreadPriority.Normal;

// Already in progress, wait for completion.
resetEvent.Wait();

// Restore the priority of the thread.
thread.Priority = priority;
}

// Must be the bitmap, and lost check->create benign data race
return (long[])previous;
}

/// <summary>
Expand All @@ -55,8 +93,9 @@ public bool ValidateJump(int destination)
/// Collects data locations in code.
/// An unset bit means the byte is an opcode, a set bit means it's data.
/// </summary>
private static long[] CreateJumpDestinationBitmap(ReadOnlySpan<byte> code)
private long[] CreateJumpDestinationBitmap()
{
ReadOnlySpan<byte> code = MachineCode.Span;
long[] jumpDestinationBitmap = new long[GetInt64ArrayLengthFromBitLength(code.Length)];
int programCounter = 0;
// We accumulate each array segment to a register and then flush to memory when we move to next.
Expand Down Expand Up @@ -146,6 +185,7 @@ private static long[] CreateJumpDestinationBitmap(ReadOnlySpan<byte> code)
/// <summary>
/// Checks if the position is in a code segment.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsJumpDestination(long[] bitvec, int pos)
{
int vecIndex = pos >> BitShiftPerInt64;
Expand All @@ -164,8 +204,28 @@ private static void MarkJumpDestinations(long[] jumpDestinationBitmap, int pos,

public void Execute()
{
// This is to support background thread preparation of the bitmap.
_jumpDestinationBitmap ??= CreateJumpDestinationBitmap(MachineCode.Span);
if (_jumpDestinationBitmap is null && Volatile.Read(ref _analysisComplete) is null)
{
ManualResetEventSlim analysisComplete = new(initialState: false);
if (Interlocked.CompareExchange(ref _analysisComplete, analysisComplete, null) is null)
{
Thread thread = Thread.CurrentThread;
ThreadPriority priority = thread.Priority;
// Boost the priority of the thread as block processing may be waiting on this.
thread.Priority = ThreadPriority.AboveNormal;

_jumpDestinationBitmap ??= CreateJumpDestinationBitmap();

// Release the MRES to be GC'd
_analysisComplete = _jumpDestinationBitmap;
// Signal complete.
analysisComplete.Set();
// Restore the priority of the thread.
thread.Priority = priority;
}
}
}

public bool RequiresAnalysis => _jumpDestinationBitmap is null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ protected virtual TransactionResult IncrementNonce(Transaction tx, BlockHeader h
? new(tx.Data ?? Memory<byte>.Empty)
: VirtualMachine.GetCachedCodeInfo(WorldState, recipient, spec);

codeInfo.AnalyseInBackgroundIfRequired();

byte[] inputData = tx.IsMessageCall ? tx.Data.AsArray() ?? Array.Empty<byte>() : Array.Empty<byte>();

return new ExecutionEnvironment
Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Nethermind.Evm/VirtualMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,7 @@ public TransactionSubstate Run<TTracingActions>(EvmState state, IWorldState worl
public void InsertCode(ReadOnlyMemory<byte> code, Address callCodeOwner, IReleaseSpec spec)
{
var codeInfo = new CodeInfo(code);
// Start generating the JumpDestinationBitmap in background.
ThreadPool.UnsafeQueueUserWorkItem(codeInfo, preferLocal: false);
codeInfo.AnalyseInBackgroundIfRequired();

Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span);
_state.InsertCode(callCodeOwner, codeHash, code, spec);
Expand Down Expand Up @@ -2492,6 +2491,7 @@ private EvmExceptionType InstructionSelfDestruct<TTracing>(EvmState vmState, ref
// pointing to data in this tx and will become invalid
// for another tx as returned to pool.
CodeInfo codeInfo = new(initCode);
codeInfo.AnalyseInBackgroundIfRequired();

ExecutionEnvironment callEnv = new
(
Expand Down

0 comments on commit 0007a71

Please sign in to comment.