509 lines
26 KiB
C++
509 lines
26 KiB
C++
/*
|
|
* AmlInterpreter.hpp
|
|
* AML bytecode interpreter — parses DSDT/SSDT into the ACPI namespace
|
|
* and evaluates methods, fields, and device status
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#pragma once
|
|
#include "AmlNamespace.hpp"
|
|
#include <CppLib/Spinlock.hpp>
|
|
#include <cstdint>
|
|
|
|
namespace Hal {
|
|
namespace AML {
|
|
|
|
// ============================================================================
|
|
|
|
// Extended AML Opcodes
|
|
|
|
// ============================================================================
|
|
// Single-byte opcodes
|
|
static constexpr uint8_t ZeroOp = 0x00;
|
|
static constexpr uint8_t OneOp = 0x01;
|
|
static constexpr uint8_t AliasOp = 0x06;
|
|
static constexpr uint8_t NameOp = 0x08;
|
|
static constexpr uint8_t BytePrefix = 0x0A;
|
|
static constexpr uint8_t WordPrefix = 0x0B;
|
|
static constexpr uint8_t DWordPrefix = 0x0C;
|
|
static constexpr uint8_t StringPrefix = 0x0D;
|
|
static constexpr uint8_t QWordPrefix = 0x0E;
|
|
static constexpr uint8_t ScopeOp = 0x10;
|
|
static constexpr uint8_t BufferOp = 0x11;
|
|
static constexpr uint8_t PackageOp = 0x12;
|
|
static constexpr uint8_t VarPackageOp = 0x13;
|
|
static constexpr uint8_t MethodOp = 0x14;
|
|
static constexpr uint8_t DualNamePrefix = 0x2E;
|
|
static constexpr uint8_t MultiNamePrefix = 0x2F;
|
|
static constexpr uint8_t LocalPrefix = 0x60; // Local0..Local7 = 0x60..0x67
|
|
static constexpr uint8_t ArgPrefix = 0x68; // Arg0..Arg6 = 0x68..0x6E
|
|
static constexpr uint8_t StoreOp = 0x70;
|
|
static constexpr uint8_t AddOp = 0x72;
|
|
static constexpr uint8_t SubtractOp = 0x74;
|
|
static constexpr uint8_t MultiplyOp = 0x77;
|
|
static constexpr uint8_t ShiftLeftOp = 0x79;
|
|
static constexpr uint8_t ShiftRightOp = 0x7A;
|
|
static constexpr uint8_t AndOp = 0x7B;
|
|
static constexpr uint8_t NandOp = 0x7C;
|
|
static constexpr uint8_t OrOp = 0x7D;
|
|
static constexpr uint8_t NorOp = 0x7E;
|
|
static constexpr uint8_t XorOp = 0x7F;
|
|
static constexpr uint8_t NotOp = 0x80;
|
|
static constexpr uint8_t DerefOfOp = 0x83;
|
|
static constexpr uint8_t SizeOfOp = 0x87;
|
|
static constexpr uint8_t IndexOp = 0x88;
|
|
static constexpr uint8_t CreateDWordFieldOp = 0x8A;
|
|
static constexpr uint8_t CreateWordFieldOp = 0x8B;
|
|
static constexpr uint8_t CreateByteFieldOp = 0x8C;
|
|
static constexpr uint8_t CreateBitFieldOp = 0x8D;
|
|
static constexpr uint8_t OnesOp = 0xFF;
|
|
static constexpr uint8_t ReturnOp = 0xA4;
|
|
static constexpr uint8_t BreakOp = 0xA5;
|
|
static constexpr uint8_t IfOp = 0xA0;
|
|
static constexpr uint8_t ElseOp = 0xA1;
|
|
static constexpr uint8_t WhileOp = 0xA2;
|
|
static constexpr uint8_t NoopOp = 0xA3;
|
|
static constexpr uint8_t ConcatOp = 0x73;
|
|
static constexpr uint8_t ToIntegerOp = 0x99;
|
|
static constexpr uint8_t ToBufferOp = 0x96;
|
|
static constexpr uint8_t RevisionOp = 0x30; // not a real AML op, used internally
|
|
|
|
// ExtOp prefix (0x5B) followed by second byte
|
|
static constexpr uint8_t ExtOpPrefix = 0x5B;
|
|
static constexpr uint8_t MutexOp = 0x01; // after ExtOpPrefix
|
|
static constexpr uint8_t EventOp = 0x02;
|
|
static constexpr uint8_t OpRegionOp = 0x80;
|
|
static constexpr uint8_t FieldOp = 0x81;
|
|
static constexpr uint8_t DeviceOp = 0x82;
|
|
static constexpr uint8_t ProcessorOp = 0x83;
|
|
static constexpr uint8_t PowerResOp = 0x84;
|
|
static constexpr uint8_t ThermalZoneOp = 0x85;
|
|
static constexpr uint8_t IndexFieldOp = 0x86;
|
|
static constexpr uint8_t BankFieldOp = 0x87;
|
|
static constexpr uint8_t AcquireOp = 0x23;
|
|
static constexpr uint8_t ReleaseOp = 0x27;
|
|
static constexpr uint8_t SleepOp = 0x22;
|
|
static constexpr uint8_t StallOp = 0x21;
|
|
static constexpr uint8_t LNotOp = 0x92; // single-byte, actually
|
|
static constexpr uint8_t LEqualOp = 0x93; // single-byte
|
|
static constexpr uint8_t LGreaterOp = 0x94; // single-byte
|
|
static constexpr uint8_t LLessOp = 0x95; // single-byte
|
|
static constexpr uint8_t LAndOp = 0x90; // single-byte
|
|
static constexpr uint8_t LOrOp = 0x91; // single-byte
|
|
static constexpr uint8_t IncrementOp = 0x75;
|
|
static constexpr uint8_t DecrementOp = 0x76;
|
|
static constexpr uint8_t DivideOp = 0x78;
|
|
static constexpr uint8_t ModOp = 0x85;
|
|
static constexpr uint8_t ConcatResOp = 0x84;
|
|
static constexpr uint8_t ToHexStringOp = 0x98;
|
|
static constexpr uint8_t ToDecimalStringOp = 0x97;
|
|
static constexpr uint8_t ExternalOp = 0x15;
|
|
static constexpr uint8_t RefOfOp = 0x71;
|
|
static constexpr uint8_t FindSetLeftBitOp = 0x81;
|
|
static constexpr uint8_t FindSetRightBitOp = 0x82;
|
|
static constexpr uint8_t NotifyOp = 0x86;
|
|
static constexpr uint8_t MatchOp = 0x89;
|
|
static constexpr uint8_t ObjectTypeOp = 0x8E;
|
|
static constexpr uint8_t CreateQWordFieldOp = 0x8F;
|
|
static constexpr uint8_t ToStringOp = 0x9C;
|
|
static constexpr uint8_t CopyObjectOp = 0x9D;
|
|
static constexpr uint8_t MidOp = 0x9E;
|
|
static constexpr uint8_t ContinueOp = 0x9F;
|
|
static constexpr uint8_t BreakPointOp = 0xCC;
|
|
|
|
// More extended opcodes (after ExtOpPrefix)
|
|
static constexpr uint8_t CondRefOfOp = 0x12;
|
|
static constexpr uint8_t CreateFieldOp = 0x13;
|
|
static constexpr uint8_t LoadTableOp = 0x1F;
|
|
static constexpr uint8_t LoadOp = 0x20;
|
|
static constexpr uint8_t SignalOp = 0x24;
|
|
static constexpr uint8_t WaitOp = 0x25;
|
|
static constexpr uint8_t ResetOp = 0x26;
|
|
static constexpr uint8_t FromBCDOp = 0x28;
|
|
static constexpr uint8_t ToBCDOp = 0x29;
|
|
static constexpr uint8_t UnloadOp = 0x2A;
|
|
static constexpr uint8_t DebugOp = 0x31;
|
|
static constexpr uint8_t FatalOp = 0x32;
|
|
static constexpr uint8_t TimerOp = 0x33;
|
|
static constexpr uint8_t DataRegionOp = 0x88;
|
|
|
|
// ============================================================================
|
|
|
|
// Interpreter Configuration
|
|
|
|
// ============================================================================
|
|
static constexpr int MaxCallDepth = 16;
|
|
|
|
// ACPICA's ceiling. Firmware polls hardware in While loops with a
|
|
// Sleep or Stall in the body (link training, power-good, L2/L3 ready)
|
|
// and expects more than a token number of passes.
|
|
static constexpr int MaxLoopIterations = 0xFFFF;
|
|
|
|
// Backstop cap on evaluation nesting. This counter is shared by
|
|
// expression evaluation AND block execution, and block nesting is
|
|
// legitimately deep -- every If/Else/While body is another level, so a
|
|
// long firmware method reaches well past a handful. The real protection
|
|
// against running off the stack is HaveStackHeadroom(), which measures
|
|
// actual usage; this only catches a decode that recurses without
|
|
// consuming input.
|
|
static constexpr int MaxExprDepth = 48;
|
|
|
|
// How much stack the interpreter may consume before it gives up. Note
|
|
// this is an ALLOWANCE, not a reserve: frames here are expensive (a
|
|
// name-resolving EvalTerm frame carries a 256-byte path buffer plus
|
|
// 272-byte Object temporaries, so ~800 bytes each), and a real
|
|
// firmware _BIF nests far enough to need tens of them. Sized against
|
|
// Sched::StackPages -- keep well under it, since the syscall path below
|
|
// has already spent some of that stack.
|
|
static constexpr uint64_t StackBudget = 32768;
|
|
|
|
// ============================================================================
|
|
|
|
// Interpreter
|
|
|
|
// ============================================================================
|
|
class Interpreter {
|
|
public:
|
|
Interpreter();
|
|
|
|
// Parse a DSDT or SSDT table into the namespace. Tables may be
|
|
// loaded one after another; each method remembers which table
|
|
// its body lives in. tableData points to the CommonSDTHeader
|
|
// (HHDM-mapped) and must stay mapped.
|
|
bool LoadTable(void* tableData);
|
|
|
|
// Evaluate a named object, returning its value.
|
|
// For methods, executes them with no arguments.
|
|
bool EvaluateObject(const char* path, Object& result);
|
|
|
|
// Evaluate a method with arguments.
|
|
bool EvaluateMethod(const char* path, const Object* args, int argCount, Object& result);
|
|
|
|
// Evaluate a node: a method is run with `args`, a field is read,
|
|
// anything else hands back its value (non-data objects as a
|
|
// Reference to the node).
|
|
bool EvaluateNode(int32_t node, const Object* args, int argCount, Object& result);
|
|
|
|
// Find the child of `parent` named `seg` ("_PS3", "_ON"), -1 if
|
|
// there is none. Short names are padded with '_'.
|
|
int32_t FindChild(int32_t parent, const char* seg) const;
|
|
|
|
// EvaluateNode() on a named child. False when it does not exist.
|
|
bool EvaluateChild(int32_t parent, const char* seg, const Object* args,
|
|
int argCount, Object& result);
|
|
|
|
// Turn a Reference, or a NameRef/String naming an object relative
|
|
// to `scopeNode`, into a node index. -1 when it names nothing.
|
|
int32_t ResolveReference(const Object& obj, int32_t scopeNode) const;
|
|
|
|
// The PCI function a Device node describes: its _ADR on the bus
|
|
// behind its parent (a root bridge's _BBN, or a PCI-to-PCI
|
|
// bridge's secondary bus). False when it cannot be placed.
|
|
bool GetPciAddress(int32_t deviceNode, uint8_t& bus, uint8_t& dev, uint8_t& fn);
|
|
|
|
// Read a field value. Returns integer.
|
|
bool ReadField(int32_t nodeIndex, uint64_t& value);
|
|
|
|
// Write a field value.
|
|
bool WriteField(int32_t nodeIndex, uint64_t value);
|
|
|
|
// Get the namespace for direct queries.
|
|
Namespace& GetNamespace() { return m_ns; }
|
|
const Namespace& GetNamespace() const { return m_ns; }
|
|
|
|
// Check if the interpreter has been initialized
|
|
bool IsInitialized() const { return m_initialized; }
|
|
|
|
// Nodes [0, DsdtNodeCount()) came from the DSDT. Nodes past it were
|
|
// added by SSDTs (or by methods run since).
|
|
int32_t DsdtNodeCount() const { return m_dsdtNodeCount; }
|
|
|
|
// Log every opcode executed, with its offset, for the next
|
|
// `budget` opcodes. The only way to see where a method that runs to
|
|
// completion without returning a value actually goes.
|
|
void SetTrace(int budget) { m_traceBudget = budget; }
|
|
|
|
// ============================================================================
|
|
|
|
// Packages
|
|
|
|
// ============================================================================
|
|
// Read one element of a Package object. Works for both a package
|
|
// whose elements were computed at run time (materialized in the
|
|
// package store) and one that is still just the constant AML bytes
|
|
// it was declared with. A name in a constant package comes back as
|
|
// a NameRef unless it resolves (from the root) to plain data, in
|
|
// which case its value is returned. Returns false when the object
|
|
// is not a package or the index is out of range.
|
|
bool GetPackageElement(const Object& package, int index, Object& result);
|
|
|
|
// Number of elements a Package declares.
|
|
int GetPackageCount(const Object& package);
|
|
|
|
// Give a package stored in a namespace node real per-element
|
|
// storage so Index() can be written through. Returns the store
|
|
// index, or -1 if the node does not hold a package.
|
|
int32_t MaterializeNodePackage(int32_t nodeIndex);
|
|
|
|
private:
|
|
struct ExecContext;
|
|
|
|
// ============================================================================
|
|
// Parsing (table load)
|
|
// ============================================================================
|
|
// ctx is the method being executed when a definition appears in
|
|
// a method body, nullptr at table load. Operands that are TermArgs
|
|
// (region offsets, buffer sizes, bank values) are evaluated in it.
|
|
bool ParseBlock(const uint8_t* aml, uint32_t offset, uint32_t endOffset,
|
|
int32_t scopeNode);
|
|
|
|
bool ParseNamedObject(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
|
|
int32_t scopeNode, ExecContext* ctx = nullptr);
|
|
|
|
bool ParseExtendedOp(const uint8_t* aml, uint32_t* pos, uint32_t endOffset,
|
|
int32_t scopeNode, ExecContext* ctx = nullptr);
|
|
|
|
// The FieldList shared by Field, IndexField and BankField.
|
|
void ParseFieldList(const uint8_t* aml, uint32_t* pos, uint32_t fieldEnd,
|
|
int32_t scopeNode, uint8_t fieldFlags, const Object& proto);
|
|
|
|
// Evaluate a TermArg operand of a definition: in the running
|
|
// method, or in the table-load context.
|
|
bool EvalOperand(ExecContext* ctx, const uint8_t* aml, uint32_t* pos,
|
|
uint32_t endOffset, int32_t scopeNode, Object& result);
|
|
|
|
// CreateBitField .. CreateQWordField and CreateField.
|
|
bool CreateBufferField(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
|
|
uint8_t op);
|
|
|
|
// ============================================================================
|
|
|
|
// Name resolution
|
|
|
|
// ============================================================================
|
|
// Read a NameString from AML and produce an absolute path.
|
|
// Advances *pos past the name.
|
|
// Produces the absolute path (rebased onto scopeNode for a
|
|
// relative name). outRelative, when given, receives the name as it
|
|
// actually appeared in the AML, and *outSearchable says whether the
|
|
// ACPI search rule applies to it -- true only when the name carried
|
|
// no root or parent prefix. Resolution must fall back to
|
|
// Namespace::SearchUpward() in that case: the absolute path is a
|
|
// guess at one scope, and the object usually lives in a parent.
|
|
int ReadNameString(const uint8_t* aml, uint32_t* pos, int32_t scopeNode,
|
|
char* outPath, int maxLen,
|
|
char* outRelative = nullptr, int relLen = 0,
|
|
bool* outSearchable = nullptr);
|
|
|
|
// Read a NameString and resolve it with the search rule. -1 when
|
|
// it names nothing.
|
|
int32_t ReadAndResolveName(const uint8_t* aml, uint32_t* pos, int32_t scopeNode);
|
|
|
|
// Read a single 4-char NameSeg from AML. Advances *pos.
|
|
void ReadNameSeg(const uint8_t* aml, uint32_t* pos, char* outSeg);
|
|
|
|
// ============================================================================
|
|
|
|
// Value decoding
|
|
|
|
// ============================================================================
|
|
uint32_t DecodePkgLength(const uint8_t* aml, uint32_t* pos);
|
|
|
|
// Decode one constant package element out of raw AML bytes and
|
|
// advance *pos past it. Names come back as NameRef; locals and
|
|
// anything else that needs a running method decode to integer 0.
|
|
bool DecodeConstElement(const uint8_t* data, uint32_t length,
|
|
uint32_t* pos, Object& out) const;
|
|
uint64_t DecodeInteger(const uint8_t* aml, uint32_t* pos);
|
|
|
|
// ============================================================================
|
|
|
|
// Method execution
|
|
|
|
// ============================================================================
|
|
struct ExecContext {
|
|
const uint8_t* Aml;
|
|
uint32_t AmlBase; // start of the block within the table
|
|
uint32_t AmlLength;
|
|
int32_t ScopeNode;
|
|
Object Locals[MaxMethodLocals];
|
|
Object Args[MaxMethodArgs];
|
|
Object ReturnValue;
|
|
bool Returned;
|
|
bool Broken;
|
|
bool Continued;
|
|
bool Aborted; // undecodable AML: unwind the method
|
|
int Depth; // method-call nesting
|
|
int ExprDepth; // expression/block nesting
|
|
};
|
|
|
|
// Increments a depth counter for the duration of a scope; pairs
|
|
// with the MaxExprDepth check at each recursive entry point.
|
|
struct DepthGuard {
|
|
int& Value;
|
|
explicit DepthGuard(int& v) : Value(v) { Value++; }
|
|
~DepthGuard() { Value--; }
|
|
};
|
|
|
|
// Where a Store lands.
|
|
struct Target {
|
|
enum class Kind : uint8_t { None, Node, Local, Arg, PkgElement, BufByte };
|
|
Kind What = Kind::None;
|
|
int32_t Node = -1;
|
|
int Index = 0; // local/arg number, or element index
|
|
int32_t PkgStore = -1;
|
|
Object* Buf = nullptr; // BufByte: the buffer being indexed
|
|
};
|
|
|
|
bool ExecuteBlock(ExecContext& ctx, uint32_t offset, uint32_t endOffset);
|
|
bool ExecuteOpcode(ExecContext& ctx, uint32_t* pos, uint32_t endOffset);
|
|
|
|
// Evaluate a term (expression that produces a value) within an execution context.
|
|
bool EvalTerm(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
|
|
// Extended (0x5B-prefixed) opcodes in expression or statement position.
|
|
bool EvalExtOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
|
|
// EvalTerm's opcode handlers. They live out of line so the
|
|
// recursive path only carries the locals of the one running.
|
|
bool EvalBufferOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalPackageOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalBinary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalUnary(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalDivide(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalStoreOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalReference(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalIndexOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalMatchOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalMidOp(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool EvalNameRef(ExecContext& ctx, uint32_t* pos, uint32_t endOffset, Object& result);
|
|
bool CallMethod(ExecContext& ctx, int32_t node, uint32_t* pos, uint32_t endOffset,
|
|
Object& result);
|
|
bool IndexElement(const Object& source, uint64_t idx, Object& out);
|
|
void ReportUnresolved(ExecContext& ctx, uint32_t namePos);
|
|
|
|
// Target forms that need operand evaluation of their own.
|
|
bool EvalIndexTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
|
bool EvalDerefTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
|
|
|
// A package element: like EvalTerm, except that a name is a
|
|
// reference to the object rather than a call or a read.
|
|
bool EvalPackageElement(ExecContext& ctx, uint32_t* pos, uint32_t endOffset,
|
|
Object& result);
|
|
|
|
// Parse a SuperName/Target operand.
|
|
bool EvalTarget(ExecContext& ctx, uint32_t* pos, Target& target);
|
|
|
|
// Store a value to a target, with ACPI's implicit conversion for
|
|
// named data objects.
|
|
void StoreToTarget(ExecContext& ctx, const Object& value, const Target& target);
|
|
|
|
// Parse a Target operand and store `value` there.
|
|
void StoreResult(ExecContext& ctx, uint32_t* pos, const Object& value);
|
|
|
|
// The current value behind a target (for Increment and friends).
|
|
Object ReadTarget(ExecContext& ctx, const Target& target);
|
|
|
|
// Run a method node. Caller holds m_evalLock.
|
|
bool InvokeMethodNode(int32_t node, const Object* args, int argCount,
|
|
Object& result, int depth, int exprDepth);
|
|
|
|
// Run a method in a context already acquired and holding its
|
|
// arguments; releases the context.
|
|
bool RunMethodIn(ExecContext* ctx, int32_t node, int depth, int exprDepth,
|
|
Object& result);
|
|
|
|
// The value of a node as an operand: fields read, methods run
|
|
// without arguments, non-data objects become References.
|
|
bool NodeValue(int32_t node, Object& result, int depth, int exprDepth);
|
|
|
|
// Log an opcode we cannot decode and stop the method: carrying on
|
|
// past it would execute operand bytes as code.
|
|
bool Abort(ExecContext& ctx, uint32_t pos, const char* why);
|
|
|
|
// Native methods (\_OSI).
|
|
bool RunNativeMethod(uint8_t which, const Object* args, int argCount, Object& result);
|
|
|
|
// Install the OS-defined objects (\_OSI, \_REV, \_OS_, \_GL_).
|
|
void InstallPredefined();
|
|
|
|
// Device identification (for PCI_Config regions).
|
|
bool IntegerChild(int32_t device, const char* seg, uint64_t fallback,
|
|
uint64_t& value, int depth);
|
|
bool IsPciRootBridge(int32_t device, int depth);
|
|
bool ResolvePciAddress(int32_t node, uint8_t& bus, uint8_t& dev,
|
|
uint8_t& fn, int depth);
|
|
|
|
// ============================================================================
|
|
|
|
// Field I/O
|
|
|
|
// ============================================================================
|
|
// Access `bits` (little-endian, bitLength long) of a field unit.
|
|
bool AccessField(int32_t fieldNode, bool write, uint8_t* bits);
|
|
bool ReadFieldObject(int32_t fieldNode, Object& value);
|
|
bool WriteFieldObject(int32_t fieldNode, const Object& value);
|
|
|
|
// One aligned access unit of a field's backing store.
|
|
bool FieldUnitIo(const Object& field, uint64_t byteOffset, uint32_t width,
|
|
bool write, uint64_t& value);
|
|
|
|
// Region access at byte `offset` into the region, `width` bytes.
|
|
bool ReadRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t& value);
|
|
bool WriteRegion(int32_t regionNode, uint64_t offset, uint32_t width, uint64_t value);
|
|
bool RegionIo(int32_t regionNode, uint64_t offset, uint32_t width,
|
|
bool write, uint64_t& value);
|
|
|
|
bool ReadBufferField(const Object& field, Object& value);
|
|
bool WriteBufferField(const Object& field, const Object& value);
|
|
|
|
// ============================================================================
|
|
|
|
// State
|
|
|
|
// ============================================================================
|
|
// ExecContext is ~4.4 KB (sixteen Objects, each carrying a
|
|
// 256-byte buffer inline). One per nested method call on a 16 KiB
|
|
// kernel stack overflows after three levels, so they come from a
|
|
// pool instead. The pool is only safe because evaluation is
|
|
// serialized -- see m_evalLock.
|
|
// Stack-exhaustion check; `frame` is the address of a local in
|
|
// the calling frame.
|
|
bool HaveStackHeadroom(const void* frame) const;
|
|
|
|
// One-shot warning when the nesting backstop trips.
|
|
static void ReportDepthLimit();
|
|
|
|
ExecContext* AcquireContext();
|
|
void ReleaseContext();
|
|
|
|
// Run `body` as a top-level evaluation: serialized, with the stack
|
|
// budget anchored here.
|
|
template<typename Fn> bool TopLevel(Fn body);
|
|
|
|
Namespace m_ns;
|
|
bool m_initialized;
|
|
bool m_predefined;
|
|
int32_t m_dsdtNodeCount;
|
|
|
|
ExecContext* m_ctxPool; // MaxCallDepth + 1 entries, heap
|
|
int m_ctxInUse;
|
|
int m_evalNesting; // top-level evaluations in progress
|
|
uint64_t m_stackFloor; // stack address the interpreter stops at
|
|
int m_traceBudget; // opcodes left to log, 0 = off
|
|
ExecContext* m_loadCtx; // table-load operand context
|
|
uint64_t m_delayedUs; // total Sleep/Stall time requested
|
|
int m_methodScan; // >0 while scanning a method body at load
|
|
kcp::Mutex m_evalLock;
|
|
};
|
|
|
|
// ============================================================================
|
|
|
|
// Global interpreter instance
|
|
|
|
// ============================================================================
|
|
Interpreter& GetInterpreter();
|
|
|
|
};
|
|
};
|