// 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); } }