// Copyright (C) 2015-2026 The Neo Project.
//
// ExecutionEngine.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.
using Neo.VM.Types;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Neo.VM;
///
/// Represents the VM used to execute the script.
///
public class ExecutionEngine : IDisposable
{
private VMState _state = VMState.BREAK;
internal bool isJumping = false;
public JumpTable JumpTable { get; }
///
/// Restrictions on the VM.
///
public ExecutionEngineLimits Limits { get; }
///
/// Used for reference counting of objects in the VM.
///
public IReferenceCounter ReferenceCounter { get; }
///
/// The invocation stack of the VM.
///
public Stack InvocationStack { get; } = new();
///
/// The top frame of the invocation stack.
///
public ExecutionContext? CurrentContext { get; private set; }
///
/// The bottom frame of the invocation stack.
///
public ExecutionContext? EntryContext { get; private set; }
///
/// The stack to store the return values.
///
public EvaluationStack ResultStack { get; }
///
/// The VM object representing the uncaught exception.
///
public StackItem? UncaughtException { get; internal set; }
///
/// The current state of the VM.
///
public VMState State
{
get
{
return _state;
}
protected internal set
{
if (_state != value)
{
_state = value;
OnStateChanged();
}
}
}
///
/// Initializes a new instance of the class.
///
/// The jump table to be used.
public ExecutionEngine(JumpTable? jumpTable = null)
: this(jumpTable, new ReferenceCounter(), ExecutionEngineLimits.Default) { }
///
/// Initializes a new instance of the class
/// with the specified and .
///
/// The jump table to be used.
/// The reference counter to be used.
/// Restrictions on the VM.
internal ExecutionEngine(JumpTable? jumpTable, IReferenceCounter referenceCounter, ExecutionEngineLimits limits)
{
JumpTable = jumpTable ?? JumpTable.Default;
Limits = limits;
ReferenceCounter = referenceCounter;
ResultStack = new(referenceCounter);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
InvocationStack.Clear();
}
}
///
/// Start execution of the VM.
///
///
public virtual VMState Execute()
{
if (State == VMState.BREAK)
State = VMState.NONE;
while (State != VMState.HALT && State != VMState.FAULT)
ExecuteNext();
return State;
}
///
/// Execute the next instruction.
///
protected internal void ExecuteNext()
{
if (InvocationStack.Count == 0)
{
State = VMState.HALT;
}
else
{
try
{
ExecutionContext context = CurrentContext!;
Instruction? currentInstruction = context.CurrentInstruction;
Instruction instruction = currentInstruction ?? Instruction.RET;
RunStats runStats = default;
PreExecuteInstruction(instruction);
#if VMPERF
Console.WriteLine("op:["
+ this.CurrentContext.InstructionPointer.ToString("X04")
+ "]"
+ this.CurrentContext.CurrentInstruction?.OpCode
+ " "
+ this.CurrentContext.EvaluationStack);
#endif
try
{
JumpTable[instruction.OpCode](this, instruction, ref runStats);
}
finally
{
PostExecuteInstruction(currentInstruction, runStats);
}
if (!isJumping && currentInstruction != null)
context.InstructionPointer += instruction.Size;
isJumping = false;
}
catch (Exception e)
{
OnFault(e);
}
}
}
///
/// Loads the specified context into the invocation stack.
///
/// The context to load.
public virtual void LoadContext(ExecutionContext context)
{
if (InvocationStack.Count >= Limits.MaxInvocationStackSize)
throw new InvalidOperationException($"MaxInvocationStackSize exceed: {InvocationStack.Count}");
InvocationStack.Push(context);
EntryContext ??= context;
CurrentContext = context;
}
///
/// Called when a context is unloaded.
///
/// The context being unloaded.
internal protected virtual void ContextUnloaded(ExecutionContext context)
{
if (InvocationStack.Count == 0)
{
CurrentContext = null;
EntryContext = null;
}
else
{
CurrentContext = InvocationStack.Peek();
}
if (context.StaticFields != null && context.StaticFields != CurrentContext?.StaticFields)
{
context.StaticFields.ClearReferences();
}
context.LocalVariables?.ClearReferences();
context.Arguments?.ClearReferences();
}
///
/// Create a new context with the specified script without loading.
///
/// The script used to create the context.
/// The number of values that the context should return when it is unloaded.
/// The pointer indicating the current instruction.
/// The created context.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected ExecutionContext CreateContext(Script script, int rvcount, int initialPosition)
{
return new ExecutionContext(script, rvcount, ReferenceCounter)
{
InstructionPointer = initialPosition
};
}
///
/// Create a new context with the specified script and load it.
///
/// The script used to create the context.
/// The number of values that the context should return when it is unloaded.
/// The pointer indicating the current instruction.
/// The created context.
public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialPosition = 0)
{
var context = CreateContext(script, rvcount, initialPosition);
LoadContext(context);
return context;
}
///
/// Called when an exception that cannot be caught by the VM is thrown.
///
/// The exception that caused the state.
protected virtual void OnFault(Exception ex)
{
State = VMState.FAULT;
#if VMPERF
if (ex != null)
{
Console.Error.WriteLine(ex);
}
#endif
}
///
/// Called when the state of the VM changed.
///
protected virtual void OnStateChanged()
{
}
///
/// Returns the item at the specified index from the top of the current stack without removing it.
///
/// The index of the object from the top of the stack.
/// The item at the specified index.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public StackItem Peek(int index = 0)
{
return CurrentContext!.EvaluationStack.Peek(index);
}
///
/// Removes and returns the item at the top of the current stack.
///
/// The item removed from the top of the stack.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public StackItem Pop()
{
return CurrentContext!.EvaluationStack.Pop();
}
///
/// Removes and returns the item at the top of the current stack without reference counting.
///
/// The item removed from the top of the stack.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal StackItem PopNoRef()
{
return CurrentContext!.EvaluationStack.PopNoRef();
}
///
/// Removes and returns the item at the top of the current stack and convert it to the specified type.
///
/// The type to convert to.
/// The item removed from the top of the stack.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Pop() where T : StackItem
{
return CurrentContext!.EvaluationStack.Pop();
}
///
/// Removes and returns the item at the top of the current stack and convert it to the specified type
/// without reference counting.
///
/// The type to convert to.
/// The item removed from the top of the stack.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal T PopNoRef() where T : StackItem
{
return CurrentContext!.EvaluationStack.PopNoRef();
}
///
/// Called after an instruction is executed.
///
protected virtual void PostExecuteInstruction(Instruction? instruction, RunStats runStats)
{
ReferenceCounter.PostExecuteInstruction();
}
///
/// Called before an instruction is executed.
///
protected virtual void PreExecuteInstruction(Instruction instruction) { }
///
/// Pushes an item onto the top of the current stack.
///
/// The item to be pushed.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Push(StackItem item)
{
CurrentContext!.EvaluationStack.Push(item);
}
///
/// Pushes item to the stack and adds exactly the specified
/// value to the reference counter. It's a perfect method to ruin the counter,
/// so use carefully.
///
/// The item to be pushed.
/// The value by which the reference counter increases.s
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void PushItemCounted(StackItem item, int count)
{
CurrentContext!.EvaluationStack.PushItemCounted(item, count);
}
}