zexus 1.7.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1187 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1425 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +485 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +13861 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +52 -11
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3589 -588
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +30 -4
- package/src/zexus.egg-info/SOURCES.txt +133 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
package/src/zexus/vm/vm.py
CHANGED
|
@@ -15,6 +15,7 @@ import os
|
|
|
15
15
|
import sys
|
|
16
16
|
import time
|
|
17
17
|
import asyncio
|
|
18
|
+
import threading
|
|
18
19
|
import importlib
|
|
19
20
|
import hashlib
|
|
20
21
|
import types
|
|
@@ -92,6 +93,60 @@ except ImportError:
|
|
|
92
93
|
PeepholeOptimizer = None
|
|
93
94
|
OptimizationLevel = None
|
|
94
95
|
|
|
96
|
+
# Bytecode Optimizer (Phase 8)
|
|
97
|
+
try:
|
|
98
|
+
from .optimizer import BytecodeOptimizer
|
|
99
|
+
_BYTECODE_OPTIMIZER_AVAILABLE = True
|
|
100
|
+
except ImportError:
|
|
101
|
+
_BYTECODE_OPTIMIZER_AVAILABLE = False
|
|
102
|
+
BytecodeOptimizer = None
|
|
103
|
+
|
|
104
|
+
# Cached Action/Lambda types for hot-path sync checks
|
|
105
|
+
_ZAction = None
|
|
106
|
+
_ZLambda = None
|
|
107
|
+
_security_mod = None
|
|
108
|
+
_iscoroutinefunction = asyncio.iscoroutinefunction
|
|
109
|
+
|
|
110
|
+
def _get_action_types():
|
|
111
|
+
global _ZAction, _ZLambda
|
|
112
|
+
if _ZAction is None:
|
|
113
|
+
try:
|
|
114
|
+
from ..object import Action as _A, LambdaFunction as _L
|
|
115
|
+
_ZAction = _A
|
|
116
|
+
_ZLambda = _L
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
return _ZAction, _ZLambda
|
|
120
|
+
|
|
121
|
+
def _get_security_mod():
|
|
122
|
+
global _security_mod
|
|
123
|
+
if _security_mod is None:
|
|
124
|
+
try:
|
|
125
|
+
from .. import security as _s
|
|
126
|
+
_security_mod = _s
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
return _security_mod
|
|
130
|
+
|
|
131
|
+
# Cython fast-path (optional)
|
|
132
|
+
try:
|
|
133
|
+
from . import fastops as _fastops
|
|
134
|
+
_FASTOPS_AVAILABLE = True
|
|
135
|
+
except Exception:
|
|
136
|
+
_FASTOPS_AVAILABLE = False
|
|
137
|
+
_fastops = None
|
|
138
|
+
|
|
139
|
+
# Rust VM (Phase 3 — adaptive execution)
|
|
140
|
+
try:
|
|
141
|
+
from zexus_core import RustVMExecutor as _RustVMExecutor
|
|
142
|
+
_RUST_VM_AVAILABLE = True
|
|
143
|
+
except Exception:
|
|
144
|
+
_RUST_VM_AVAILABLE = False
|
|
145
|
+
_RustVMExecutor = None
|
|
146
|
+
|
|
147
|
+
# Sentinel returned when Rust VM signals needs_fallback
|
|
148
|
+
_RUST_VM_FALLBACK_SENTINEL = object()
|
|
149
|
+
|
|
95
150
|
# Async Optimizer (Phase 8)
|
|
96
151
|
try:
|
|
97
152
|
from .async_optimizer import AsyncOptimizer, AsyncOptimizationLevel
|
|
@@ -111,6 +166,14 @@ except ImportError:
|
|
|
111
166
|
SSAConverter = None
|
|
112
167
|
RegisterAllocator = None
|
|
113
168
|
|
|
169
|
+
# Bytecode Converter (Stack -> Register)
|
|
170
|
+
try:
|
|
171
|
+
from .bytecode_converter import BytecodeConverter
|
|
172
|
+
_BYTECODE_CONVERTER_AVAILABLE = True
|
|
173
|
+
except ImportError:
|
|
174
|
+
_BYTECODE_CONVERTER_AVAILABLE = False
|
|
175
|
+
BytecodeConverter = None
|
|
176
|
+
|
|
114
177
|
# Renderer Backend
|
|
115
178
|
try:
|
|
116
179
|
from ..renderer import backend as _BACKEND
|
|
@@ -286,6 +349,7 @@ class VM:
|
|
|
286
349
|
jit_threshold: int = 100,
|
|
287
350
|
use_memory_manager: bool = False,
|
|
288
351
|
max_heap_mb: int = 100,
|
|
352
|
+
gc_threshold: int = 1000,
|
|
289
353
|
mode: VMMode = VMMode.AUTO,
|
|
290
354
|
worker_count: int = None,
|
|
291
355
|
chunk_size: int = 50,
|
|
@@ -294,18 +358,31 @@ class VM:
|
|
|
294
358
|
debug: bool = False,
|
|
295
359
|
enable_profiling: bool = False,
|
|
296
360
|
profiling_level: str = "DETAILED",
|
|
361
|
+
profiling_sample_rate: float = 1.0,
|
|
362
|
+
profiling_max_samples: int = 2048,
|
|
363
|
+
profiling_track_overhead: bool = False,
|
|
297
364
|
enable_memory_pool: bool = True,
|
|
298
365
|
pool_max_size: int = 1000,
|
|
299
366
|
enable_peephole_optimizer: bool = True,
|
|
367
|
+
enable_bytecode_optimizer: bool = False,
|
|
368
|
+
optimizer_level: int = 2,
|
|
300
369
|
optimization_level: str = "MODERATE",
|
|
301
370
|
enable_async_optimizer: bool = True,
|
|
302
371
|
async_optimization_level: str = "MODERATE",
|
|
303
372
|
enable_ssa: bool = False,
|
|
304
373
|
enable_register_allocation: bool = False,
|
|
374
|
+
enable_bytecode_converter: bool = False,
|
|
375
|
+
converter_aggressive: bool = False,
|
|
376
|
+
fast_single_shot: bool = False,
|
|
377
|
+
single_shot_max_instructions: int = 64,
|
|
305
378
|
num_allocator_registers: int = 16,
|
|
306
379
|
enable_gas_metering: bool = True,
|
|
307
380
|
gas_limit: int = None,
|
|
308
|
-
enable_timeout: bool = True
|
|
381
|
+
enable_timeout: bool = True,
|
|
382
|
+
enable_fast_loop: bool = False,
|
|
383
|
+
fast_loop_threshold: int = 512,
|
|
384
|
+
enable_gas_light: bool = False,
|
|
385
|
+
gas_light_cost: int = 1
|
|
309
386
|
):
|
|
310
387
|
"""
|
|
311
388
|
Initialize the enhanced VM.
|
|
@@ -315,9 +392,12 @@ class VM:
|
|
|
315
392
|
self.env = env or {}
|
|
316
393
|
self._parent_env = parent_env
|
|
317
394
|
self.debug = debug
|
|
395
|
+
self._register_import_builtins()
|
|
318
396
|
|
|
319
397
|
# --- Gas Metering (Security) ---
|
|
320
398
|
self.enable_gas_metering = enable_gas_metering and _GAS_METERING_AVAILABLE
|
|
399
|
+
self.enable_gas_light = bool(enable_gas_light)
|
|
400
|
+
self.gas_light_cost = max(1, int(gas_light_cost))
|
|
321
401
|
self.gas_metering = None
|
|
322
402
|
if self.enable_gas_metering:
|
|
323
403
|
self.gas_metering = GasMetering(gas_limit=gas_limit, enable_timeout=enable_timeout)
|
|
@@ -348,6 +428,53 @@ class VM:
|
|
|
348
428
|
self._total_execution_time = 0.0
|
|
349
429
|
self._mode_usage = {m.value: 0 for m in VMMode}
|
|
350
430
|
self._last_opcode_profile = None
|
|
431
|
+
self._call_method_trace_count = 0
|
|
432
|
+
self._call_method_total = 0
|
|
433
|
+
self._method_target_trace_count = 0
|
|
434
|
+
self._action_evaluator = None
|
|
435
|
+
self._opcode_exec_count = 0
|
|
436
|
+
self._in_execution = 0
|
|
437
|
+
self._native_jit_auto_enabled = False
|
|
438
|
+
self._native_jit_auto_threshold = 700
|
|
439
|
+
|
|
440
|
+
# --- Rust VM Adaptive Routing (Phase 3 + Phase 6) ---
|
|
441
|
+
self._rust_vm_available = _RUST_VM_AVAILABLE
|
|
442
|
+
self._rust_vm_executor = _RustVMExecutor() if _RUST_VM_AVAILABLE else None
|
|
443
|
+
self._rust_vm_threshold = 0 # Phase 6: route ALL programs through Rust by default
|
|
444
|
+
try:
|
|
445
|
+
_env_thresh = os.environ.get("ZEXUS_RUST_VM_THRESHOLD")
|
|
446
|
+
if _env_thresh is not None:
|
|
447
|
+
self._rust_vm_threshold = int(_env_thresh)
|
|
448
|
+
except (ValueError, TypeError):
|
|
449
|
+
pass
|
|
450
|
+
self._rust_vm_enabled = self._rust_vm_available # Can be disabled at runtime
|
|
451
|
+
self._rust_vm_stats = {
|
|
452
|
+
"rust_executions": 0,
|
|
453
|
+
"rust_fallbacks": 0,
|
|
454
|
+
"python_executions": 0,
|
|
455
|
+
"total_rust_ops": 0,
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
self._env_version = 0
|
|
459
|
+
self._name_cache: Dict[str, Tuple[Any, int]] = {}
|
|
460
|
+
self._method_cache: Dict[Tuple[type, str], Any] = {}
|
|
461
|
+
self.enable_fast_loop = bool(enable_fast_loop)
|
|
462
|
+
try:
|
|
463
|
+
env_threshold = os.environ.get("ZEXUS_VM_FAST_LOOP_THRESHOLD")
|
|
464
|
+
if env_threshold is not None:
|
|
465
|
+
fast_loop_threshold = int(env_threshold)
|
|
466
|
+
except Exception:
|
|
467
|
+
pass
|
|
468
|
+
self.fast_loop_threshold = max(1, int(fast_loop_threshold))
|
|
469
|
+
self._fast_loop_stats: Dict[str, Any] = {"used": False, "reason": ""}
|
|
470
|
+
|
|
471
|
+
# Round 3 optimizations
|
|
472
|
+
self._isinstance_cache: Dict[Tuple[int, type], bool] = {} # Cache isinstance results
|
|
473
|
+
self._vm_pool: List[Any] = [] # Pool of reusable child VMs
|
|
474
|
+
self._vm_pool_lock = None # Will be set if pooling enabled
|
|
475
|
+
|
|
476
|
+
self.prefer_register = False
|
|
477
|
+
self.prefer_parallel = False
|
|
351
478
|
|
|
352
479
|
# --- JIT Compilation (Phase 2) ---
|
|
353
480
|
self.use_jit = use_jit and _JIT_AVAILABLE
|
|
@@ -375,7 +502,7 @@ class VM:
|
|
|
375
502
|
self._memory_lock = threading.Lock()
|
|
376
503
|
self.memory_manager = create_memory_manager(
|
|
377
504
|
max_heap_mb=max_heap_mb,
|
|
378
|
-
gc_threshold=
|
|
505
|
+
gc_threshold=gc_threshold
|
|
379
506
|
)
|
|
380
507
|
|
|
381
508
|
# --- Profiler (Phase 8) ---
|
|
@@ -384,7 +511,12 @@ class VM:
|
|
|
384
511
|
if self.enable_profiling:
|
|
385
512
|
try:
|
|
386
513
|
level = getattr(ProfilingLevel, profiling_level, ProfilingLevel.DETAILED)
|
|
387
|
-
self.profiler = InstructionProfiler(
|
|
514
|
+
self.profiler = InstructionProfiler(
|
|
515
|
+
level=level,
|
|
516
|
+
sample_rate=profiling_sample_rate,
|
|
517
|
+
max_samples=profiling_max_samples,
|
|
518
|
+
track_overhead=profiling_track_overhead
|
|
519
|
+
)
|
|
388
520
|
if debug:
|
|
389
521
|
print(f"[VM] Profiler enabled: {profiling_level}")
|
|
390
522
|
except Exception as e:
|
|
@@ -423,6 +555,20 @@ class VM:
|
|
|
423
555
|
print(f"[VM] Failed to enable peephole optimizer: {e}")
|
|
424
556
|
self.enable_peephole_optimizer = False
|
|
425
557
|
|
|
558
|
+
# --- Bytecode Optimizer (Phase 8) ---
|
|
559
|
+
self.enable_bytecode_optimizer = enable_bytecode_optimizer and _BYTECODE_OPTIMIZER_AVAILABLE
|
|
560
|
+
self.bytecode_optimizer = None
|
|
561
|
+
if self.enable_bytecode_optimizer:
|
|
562
|
+
try:
|
|
563
|
+
level = max(0, min(3, int(optimizer_level)))
|
|
564
|
+
self.bytecode_optimizer = BytecodeOptimizer(level=level, max_passes=5, debug=debug)
|
|
565
|
+
if debug:
|
|
566
|
+
print(f"[VM] Bytecode optimizer enabled: level={level}")
|
|
567
|
+
except Exception as e:
|
|
568
|
+
if debug:
|
|
569
|
+
print(f"[VM] Failed to enable bytecode optimizer: {e}")
|
|
570
|
+
self.enable_bytecode_optimizer = False
|
|
571
|
+
|
|
426
572
|
# --- Async Optimizer (Phase 8) ---
|
|
427
573
|
self.enable_async_optimizer = enable_async_optimizer and _ASYNC_OPTIMIZER_AVAILABLE
|
|
428
574
|
self.async_optimizer = None
|
|
@@ -442,6 +588,15 @@ class VM:
|
|
|
442
588
|
self.enable_register_allocation = enable_register_allocation and _SSA_AVAILABLE
|
|
443
589
|
self.ssa_converter = None
|
|
444
590
|
self.register_allocator = None
|
|
591
|
+
self.enable_bytecode_converter = enable_bytecode_converter and _BYTECODE_CONVERTER_AVAILABLE
|
|
592
|
+
self.bytecode_converter = None
|
|
593
|
+
|
|
594
|
+
# --- Fast single-shot execution ---
|
|
595
|
+
self.fast_single_shot = bool(fast_single_shot)
|
|
596
|
+
try:
|
|
597
|
+
self.single_shot_max_instructions = int(single_shot_max_instructions)
|
|
598
|
+
except Exception:
|
|
599
|
+
self.single_shot_max_instructions = 64
|
|
445
600
|
|
|
446
601
|
if self.enable_ssa:
|
|
447
602
|
try:
|
|
@@ -459,12 +614,28 @@ class VM:
|
|
|
459
614
|
num_registers=num_allocator_registers,
|
|
460
615
|
num_temp_registers=8
|
|
461
616
|
)
|
|
617
|
+
self._last_register_allocation = None
|
|
462
618
|
if debug:
|
|
463
619
|
print(f"[VM] Register allocator enabled: {num_allocator_registers} registers")
|
|
464
620
|
except Exception as e:
|
|
465
621
|
if debug:
|
|
466
622
|
print(f"[VM] Failed to enable register allocator: {e}")
|
|
467
623
|
self.enable_register_allocation = False
|
|
624
|
+
self._last_register_allocation = None
|
|
625
|
+
|
|
626
|
+
if self.enable_bytecode_converter:
|
|
627
|
+
try:
|
|
628
|
+
self.bytecode_converter = BytecodeConverter(
|
|
629
|
+
num_registers=num_registers,
|
|
630
|
+
aggressive=converter_aggressive,
|
|
631
|
+
debug=debug
|
|
632
|
+
)
|
|
633
|
+
if debug:
|
|
634
|
+
print(f"[VM] Bytecode converter enabled: aggressive={converter_aggressive}")
|
|
635
|
+
except Exception as e:
|
|
636
|
+
if debug:
|
|
637
|
+
print(f"[VM] Failed to enable bytecode converter: {e}")
|
|
638
|
+
self.enable_bytecode_converter = False
|
|
468
639
|
|
|
469
640
|
# --- Execution Mode Configuration ---
|
|
470
641
|
self.mode = mode
|
|
@@ -492,33 +663,193 @@ class VM:
|
|
|
492
663
|
if debug:
|
|
493
664
|
print(f"[VM] Initialized | Mode: {mode.value} | JIT: {self.use_jit} | MemMgr: {self.use_memory_manager}")
|
|
494
665
|
|
|
666
|
+
def _return_vm_to_pool(self, vm) -> None:
|
|
667
|
+
"""Return a child VM to the pool for reuse."""
|
|
668
|
+
if hasattr(self, "_vm_pool") and self._vm_pool is not None:
|
|
669
|
+
if len(self._vm_pool) < 1000:
|
|
670
|
+
self._vm_pool.append(vm)
|
|
671
|
+
|
|
672
|
+
@classmethod
|
|
673
|
+
def create_child(cls, parent_vm, env: Dict[str, Any], builtins: Dict[str, Any] = None):
|
|
674
|
+
"""
|
|
675
|
+
Create a lightweight child VM execution context sharing infrastructure/components from parent.
|
|
676
|
+
Avoids overhead of full initialization for function calls.
|
|
677
|
+
"""
|
|
678
|
+
vm = None
|
|
679
|
+
if hasattr(parent_vm, "_vm_pool") and parent_vm._vm_pool:
|
|
680
|
+
try:
|
|
681
|
+
vm = parent_vm._vm_pool.pop()
|
|
682
|
+
except IndexError:
|
|
683
|
+
pass
|
|
684
|
+
|
|
685
|
+
if vm is None:
|
|
686
|
+
vm = cls.__new__(cls)
|
|
687
|
+
|
|
688
|
+
# Core Context
|
|
689
|
+
vm.builtins = builtins if builtins is not None else parent_vm.builtins
|
|
690
|
+
vm.env = env
|
|
691
|
+
vm._parent_env = parent_vm
|
|
692
|
+
vm.debug = parent_vm.debug
|
|
693
|
+
vm.mode = parent_vm.mode
|
|
694
|
+
|
|
695
|
+
# Infrastructure (shared)
|
|
696
|
+
vm.use_jit = parent_vm.use_jit
|
|
697
|
+
vm.jit_compiler = parent_vm.jit_compiler
|
|
698
|
+
vm._jit_lock = parent_vm._jit_lock
|
|
699
|
+
|
|
700
|
+
vm.use_memory_manager = parent_vm.use_memory_manager
|
|
701
|
+
vm.memory_manager = parent_vm.memory_manager
|
|
702
|
+
vm._memory_lock = parent_vm._memory_lock
|
|
703
|
+
vm._managed_objects = {} # Local GC tracking
|
|
704
|
+
|
|
705
|
+
vm.enable_gas_metering = parent_vm.enable_gas_metering
|
|
706
|
+
vm.gas_metering = parent_vm.gas_metering
|
|
707
|
+
vm.enable_gas_light = getattr(parent_vm, "enable_gas_light", False)
|
|
708
|
+
vm.gas_light_cost = getattr(parent_vm, "gas_light_cost", 1)
|
|
709
|
+
|
|
710
|
+
vm.enable_profiling = parent_vm.enable_profiling
|
|
711
|
+
vm.profiler = parent_vm.profiler
|
|
712
|
+
|
|
713
|
+
# Optimizers (shared)
|
|
714
|
+
vm.enable_peephole_optimizer = parent_vm.enable_peephole_optimizer
|
|
715
|
+
vm.peephole_optimizer = parent_vm.peephole_optimizer
|
|
716
|
+
|
|
717
|
+
vm.enable_bytecode_optimizer = parent_vm.enable_bytecode_optimizer
|
|
718
|
+
vm.bytecode_optimizer = parent_vm.bytecode_optimizer
|
|
719
|
+
|
|
720
|
+
vm.enable_async_optimizer = parent_vm.enable_async_optimizer
|
|
721
|
+
vm.async_optimizer = parent_vm.async_optimizer
|
|
722
|
+
|
|
723
|
+
# Pools (shared)
|
|
724
|
+
vm.enable_memory_pool = parent_vm.enable_memory_pool
|
|
725
|
+
vm.integer_pool = parent_vm.integer_pool
|
|
726
|
+
vm.string_pool = parent_vm.string_pool
|
|
727
|
+
vm.list_pool = parent_vm.list_pool
|
|
728
|
+
|
|
729
|
+
# Advanced Features (shared)
|
|
730
|
+
vm.enable_ssa = parent_vm.enable_ssa
|
|
731
|
+
vm.ssa_converter = parent_vm.ssa_converter
|
|
732
|
+
vm.enable_register_allocation = parent_vm.enable_register_allocation
|
|
733
|
+
vm.register_allocator = parent_vm.register_allocator
|
|
734
|
+
|
|
735
|
+
vm.enable_bytecode_converter = parent_vm.enable_bytecode_converter
|
|
736
|
+
vm.bytecode_converter = parent_vm.bytecode_converter
|
|
737
|
+
|
|
738
|
+
# Execution Helpers (shared)
|
|
739
|
+
vm._register_vm = parent_vm._register_vm
|
|
740
|
+
vm._parallel_vm = parent_vm._parallel_vm
|
|
741
|
+
|
|
742
|
+
# Local State Init
|
|
743
|
+
vm._closure_cells = {}
|
|
744
|
+
vm._events = {}
|
|
745
|
+
vm._tasks = {}
|
|
746
|
+
vm._task_counter = 0
|
|
747
|
+
vm._env_version = 0
|
|
748
|
+
vm._name_cache = {}
|
|
749
|
+
vm._method_cache = {}
|
|
750
|
+
vm._execution_count = 0
|
|
751
|
+
vm._total_execution_time = 0.0
|
|
752
|
+
vm._mode_usage = {m.value: 0 for m in VMMode}
|
|
753
|
+
vm._last_opcode_profile = None
|
|
754
|
+
vm._call_method_trace_count = 0
|
|
755
|
+
vm._call_method_total = 0
|
|
756
|
+
vm._method_target_trace_count = 0
|
|
757
|
+
vm._action_evaluator = None
|
|
758
|
+
vm._opcode_exec_count = 0
|
|
759
|
+
vm._in_execution = 0
|
|
760
|
+
vm._native_jit_auto_enabled = parent_vm._native_jit_auto_enabled
|
|
761
|
+
vm._native_jit_auto_threshold = parent_vm._native_jit_auto_threshold
|
|
762
|
+
vm._perf_fast_dispatch = getattr(parent_vm, "_perf_fast_dispatch", False)
|
|
763
|
+
vm.enable_fast_loop = getattr(parent_vm, "enable_fast_loop", False)
|
|
764
|
+
vm.fast_loop_threshold = getattr(parent_vm, "fast_loop_threshold", 512)
|
|
765
|
+
vm._fast_loop_stats = {"used": False, "reason": ""}
|
|
766
|
+
|
|
767
|
+
# Settings
|
|
768
|
+
vm.worker_count = parent_vm.worker_count
|
|
769
|
+
vm.chunk_size = parent_vm.chunk_size
|
|
770
|
+
vm.num_registers = parent_vm.num_registers
|
|
771
|
+
vm.hybrid_mode = parent_vm.hybrid_mode
|
|
772
|
+
vm.fast_single_shot = parent_vm.fast_single_shot
|
|
773
|
+
vm.single_shot_max_instructions = parent_vm.single_shot_max_instructions
|
|
774
|
+
|
|
775
|
+
return vm
|
|
776
|
+
|
|
495
777
|
# ==================== VM <-> Evaluator Conversions ====================
|
|
496
778
|
|
|
497
779
|
@staticmethod
|
|
498
780
|
def _wrap_for_builtin(value: Any) -> Any:
|
|
499
|
-
|
|
781
|
+
# Fast path for primitives
|
|
782
|
+
t = type(value)
|
|
783
|
+
if t is int: return ZInteger(value)
|
|
784
|
+
if t is str: return ZString(value)
|
|
785
|
+
if t is bool: return ZBoolean(value)
|
|
786
|
+
if t is float: return ZFloat(value)
|
|
787
|
+
if value is None: return value
|
|
788
|
+
|
|
789
|
+
# Already wrapped check
|
|
790
|
+
if isinstance(value, (ZInteger, ZFloat, ZString, ZBoolean, ZList, ZMap)):
|
|
500
791
|
return value
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if
|
|
504
|
-
return ZInteger(value)
|
|
505
|
-
if isinstance(value, float):
|
|
506
|
-
return ZFloat(value)
|
|
507
|
-
if isinstance(value, str):
|
|
508
|
-
return ZString(value)
|
|
509
|
-
if isinstance(value, list):
|
|
792
|
+
|
|
793
|
+
# Recursive structures
|
|
794
|
+
if t is list:
|
|
510
795
|
return ZList([VM._wrap_for_builtin(elem) for elem in value])
|
|
511
|
-
if
|
|
796
|
+
if t is dict:
|
|
512
797
|
pairs = {}
|
|
513
798
|
for key, val in value.items():
|
|
514
|
-
|
|
799
|
+
if isinstance(key, ZString):
|
|
800
|
+
norm_key = key.value
|
|
801
|
+
elif isinstance(key, str):
|
|
802
|
+
norm_key = key
|
|
803
|
+
elif hasattr(key, "inspect"):
|
|
804
|
+
try:
|
|
805
|
+
norm_key = key.inspect()
|
|
806
|
+
except Exception:
|
|
807
|
+
norm_key = str(key)
|
|
808
|
+
else:
|
|
809
|
+
norm_key = str(key)
|
|
515
810
|
wrapped_val = VM._wrap_for_builtin(val)
|
|
516
|
-
pairs[
|
|
811
|
+
pairs[norm_key] = wrapped_val
|
|
517
812
|
return ZMap(pairs)
|
|
518
813
|
return value
|
|
519
814
|
|
|
815
|
+
@staticmethod
|
|
816
|
+
def _format_print_value(value: Any) -> str:
|
|
817
|
+
"""Format a Zexus value for print output, unwrapping object wrappers."""
|
|
818
|
+
if value is None:
|
|
819
|
+
return "null"
|
|
820
|
+
t = type(value)
|
|
821
|
+
if t is int or t is str or t is float:
|
|
822
|
+
return str(value)
|
|
823
|
+
if t is bool:
|
|
824
|
+
return "true" if value else "false"
|
|
825
|
+
if isinstance(value, (ZInteger, ZFloat)):
|
|
826
|
+
return str(value.value)
|
|
827
|
+
if isinstance(value, ZString):
|
|
828
|
+
return value.value
|
|
829
|
+
if isinstance(value, ZBoolean):
|
|
830
|
+
return "true" if value.value else "false"
|
|
831
|
+
if isinstance(value, ZNull):
|
|
832
|
+
return "null"
|
|
833
|
+
if isinstance(value, ZList):
|
|
834
|
+
items = ", ".join(VM._format_print_value(e) for e in value.elements)
|
|
835
|
+
return f"[{items}]"
|
|
836
|
+
if isinstance(value, ZMap):
|
|
837
|
+
entries = ", ".join(
|
|
838
|
+
f"{VM._format_print_value(k)}: {VM._format_print_value(v)}"
|
|
839
|
+
for k, v in value.pairs.items()
|
|
840
|
+
)
|
|
841
|
+
return "{" + entries + "}"
|
|
842
|
+
if hasattr(value, "value") and not callable(getattr(value, "value")):
|
|
843
|
+
return str(value.value)
|
|
844
|
+
return str(value)
|
|
845
|
+
|
|
520
846
|
@staticmethod
|
|
521
847
|
def _unwrap_after_builtin(value: Any) -> Any:
|
|
848
|
+
# Fast path for common types
|
|
849
|
+
t = type(value)
|
|
850
|
+
if t is int or t is str or t is float or t is bool:
|
|
851
|
+
return value
|
|
852
|
+
|
|
522
853
|
if isinstance(value, (ZInteger, ZFloat, ZBoolean, ZString)):
|
|
523
854
|
return value.value
|
|
524
855
|
if isinstance(value, ZNull):
|
|
@@ -544,6 +875,318 @@ class VM:
|
|
|
544
875
|
|
|
545
876
|
# ==================== Public Execution API ====================
|
|
546
877
|
|
|
878
|
+
def _run_coroutine_sync(self, coro):
|
|
879
|
+
"""Run a coroutine from sync code using the shared Zexus event loop.
|
|
880
|
+
|
|
881
|
+
The persistent background loop means all VM async tasks share a
|
|
882
|
+
single event loop and can coordinate via asyncio primitives.
|
|
883
|
+
"""
|
|
884
|
+
from ..event_loop import submit, is_loop_thread
|
|
885
|
+
if is_loop_thread():
|
|
886
|
+
# Already on the event-loop thread — fall back to a throwaway
|
|
887
|
+
# loop to avoid deadlock.
|
|
888
|
+
loop = asyncio.new_event_loop()
|
|
889
|
+
try:
|
|
890
|
+
return loop.run_until_complete(coro)
|
|
891
|
+
finally:
|
|
892
|
+
loop.close()
|
|
893
|
+
return submit(coro)
|
|
894
|
+
|
|
895
|
+
def _bump_env_version(self, name: Optional[str] = None, value: Any = None) -> None:
|
|
896
|
+
self._env_version += 1
|
|
897
|
+
if name is not None:
|
|
898
|
+
self._name_cache[name] = (value, self._env_version)
|
|
899
|
+
|
|
900
|
+
def _register_import_builtins(self) -> None:
|
|
901
|
+
if "__vm_use_module__" not in self.builtins:
|
|
902
|
+
self.builtins["__vm_use_module__"] = self._vm_use_module
|
|
903
|
+
if "__vm_from_module__" not in self.builtins:
|
|
904
|
+
self.builtins["__vm_from_module__"] = self._vm_from_module
|
|
905
|
+
|
|
906
|
+
def _vm_use_module(self, spec):
|
|
907
|
+
if spec is None:
|
|
908
|
+
return None
|
|
909
|
+
if isinstance(spec, ZMap):
|
|
910
|
+
spec = self._unwrap_after_builtin(spec)
|
|
911
|
+
file_path = spec.get("file", "") if isinstance(spec, dict) else ""
|
|
912
|
+
alias = spec.get("alias", "") if isinstance(spec, dict) else ""
|
|
913
|
+
names = spec.get("names", []) if isinstance(spec, dict) else []
|
|
914
|
+
is_named = bool(spec.get("is_named")) if isinstance(spec, dict) else False
|
|
915
|
+
trace_imports = os.environ.get("ZEXUS_VM_IMPORT_TRACE")
|
|
916
|
+
if trace_imports and trace_imports.lower() not in ("0", "false", "off"):
|
|
917
|
+
print(f"[VM TRACE] __vm_use_module__ file={file_path} alias={alias} names={len(names)}")
|
|
918
|
+
return self._execute_import(file_path, alias=alias, names=names, is_named=is_named)
|
|
919
|
+
|
|
920
|
+
def _vm_from_module(self, spec):
|
|
921
|
+
if spec is None:
|
|
922
|
+
return None
|
|
923
|
+
if isinstance(spec, ZMap):
|
|
924
|
+
spec = self._unwrap_after_builtin(spec)
|
|
925
|
+
file_path = spec.get("file", "") if isinstance(spec, dict) else ""
|
|
926
|
+
imports = spec.get("imports", []) if isinstance(spec, dict) else []
|
|
927
|
+
names = []
|
|
928
|
+
alias_map = {}
|
|
929
|
+
for entry in imports:
|
|
930
|
+
if isinstance(entry, dict):
|
|
931
|
+
name = entry.get("name")
|
|
932
|
+
alias = entry.get("alias")
|
|
933
|
+
elif isinstance(entry, (list, tuple)):
|
|
934
|
+
name = entry[0] if len(entry) > 0 else None
|
|
935
|
+
alias = entry[1] if len(entry) > 1 else None
|
|
936
|
+
else:
|
|
937
|
+
name = entry
|
|
938
|
+
alias = None
|
|
939
|
+
if name:
|
|
940
|
+
names.append(name)
|
|
941
|
+
if alias:
|
|
942
|
+
alias_map[name] = alias
|
|
943
|
+
return self._execute_import(file_path, alias="", names=names, is_named=True, alias_map=alias_map)
|
|
944
|
+
|
|
945
|
+
def _module_env_to_map(self, module_env):
|
|
946
|
+
if module_env is None:
|
|
947
|
+
return None
|
|
948
|
+
if isinstance(module_env, dict):
|
|
949
|
+
return module_env
|
|
950
|
+
exports = None
|
|
951
|
+
if hasattr(module_env, "get_exports"):
|
|
952
|
+
try:
|
|
953
|
+
exports = module_env.get_exports()
|
|
954
|
+
except Exception:
|
|
955
|
+
exports = None
|
|
956
|
+
store = getattr(module_env, "store", None)
|
|
957
|
+
if isinstance(store, dict):
|
|
958
|
+
if exports and isinstance(exports, dict):
|
|
959
|
+
merged = dict(store)
|
|
960
|
+
merged.update(exports)
|
|
961
|
+
return merged
|
|
962
|
+
return store
|
|
963
|
+
if isinstance(module_env, ZMap):
|
|
964
|
+
mapped = {}
|
|
965
|
+
for key, value in module_env.pairs.items():
|
|
966
|
+
if isinstance(key, ZString):
|
|
967
|
+
mapped[key.value] = value
|
|
968
|
+
else:
|
|
969
|
+
mapped[str(key)] = value
|
|
970
|
+
return mapped
|
|
971
|
+
return None
|
|
972
|
+
|
|
973
|
+
def _get_importer_file(self) -> Optional[str]:
|
|
974
|
+
importer = self.env.get("__file__") if isinstance(self.env, dict) else None
|
|
975
|
+
if importer is None:
|
|
976
|
+
return None
|
|
977
|
+
if hasattr(importer, "value"):
|
|
978
|
+
return importer.value
|
|
979
|
+
if isinstance(importer, str):
|
|
980
|
+
return importer
|
|
981
|
+
return None
|
|
982
|
+
|
|
983
|
+
def _load_zexus_module_env(self, file_path: str):
|
|
984
|
+
if os.environ.get("ZEXUS_DEBUG_COMPILER"):
|
|
985
|
+
with open("debug_compiler_fail.log", "a") as f:
|
|
986
|
+
f.write(f"[DEBUG] _load_zexus_module_env called for {file_path}\n")
|
|
987
|
+
from ..module_cache import (get_cached_module, cache_module, get_module_candidates,
|
|
988
|
+
normalize_path, invalidate_module,
|
|
989
|
+
begin_loading, end_loading, CircularImportError)
|
|
990
|
+
from ..object import Environment, String
|
|
991
|
+
from ..lexer import Lexer
|
|
992
|
+
from ..parser import Parser
|
|
993
|
+
from ..evaluator.core import Evaluator
|
|
994
|
+
|
|
995
|
+
normalized_path = normalize_path(file_path)
|
|
996
|
+
cached = get_cached_module(normalized_path)
|
|
997
|
+
if cached:
|
|
998
|
+
module_env, bytecode, ast = cached
|
|
999
|
+
return module_env
|
|
1000
|
+
|
|
1001
|
+
importer_file = self._get_importer_file()
|
|
1002
|
+
candidates = get_module_candidates(file_path, importer_file)
|
|
1003
|
+
for candidate in candidates:
|
|
1004
|
+
try:
|
|
1005
|
+
cached = get_cached_module(normalize_path(candidate))
|
|
1006
|
+
if cached:
|
|
1007
|
+
module_env, bytecode, ast = cached
|
|
1008
|
+
return module_env
|
|
1009
|
+
except Exception:
|
|
1010
|
+
continue
|
|
1011
|
+
|
|
1012
|
+
# Circular import detection
|
|
1013
|
+
try:
|
|
1014
|
+
begin_loading(normalized_path)
|
|
1015
|
+
except CircularImportError as e:
|
|
1016
|
+
raise RuntimeError(str(e)) from e
|
|
1017
|
+
|
|
1018
|
+
module_env = Environment()
|
|
1019
|
+
loaded = False
|
|
1020
|
+
compiled_bytecode = None
|
|
1021
|
+
parsed_ast = None
|
|
1022
|
+
|
|
1023
|
+
try:
|
|
1024
|
+
for candidate in candidates:
|
|
1025
|
+
try:
|
|
1026
|
+
if not os.path.exists(candidate):
|
|
1027
|
+
continue
|
|
1028
|
+
with open(candidate, "r", encoding="utf-8") as handle:
|
|
1029
|
+
code = handle.read()
|
|
1030
|
+
lexer = Lexer(code)
|
|
1031
|
+
parser = Parser(lexer)
|
|
1032
|
+
program = parser.parse_program()
|
|
1033
|
+
if getattr(parser, "errors", None):
|
|
1034
|
+
continue
|
|
1035
|
+
|
|
1036
|
+
parsed_ast = program
|
|
1037
|
+
module_env.set("__file__", String(os.path.abspath(candidate)))
|
|
1038
|
+
module_env.set("__MODULE__", String(file_path))
|
|
1039
|
+
|
|
1040
|
+
# Try to compile to bytecode and execute via VM (fast path)
|
|
1041
|
+
try:
|
|
1042
|
+
from .compiler import BytecodeCompiler
|
|
1043
|
+
|
|
1044
|
+
# Phase 1: check for co-located .zxc binary first
|
|
1045
|
+
try:
|
|
1046
|
+
from .binary_bytecode import load_zxc, save_zxc, is_zxc_fresh, zxc_path_for
|
|
1047
|
+
_zxc_path = zxc_path_for(candidate)
|
|
1048
|
+
if is_zxc_fresh(candidate):
|
|
1049
|
+
compiled_bytecode = load_zxc(_zxc_path)
|
|
1050
|
+
except Exception:
|
|
1051
|
+
pass
|
|
1052
|
+
|
|
1053
|
+
if compiled_bytecode is None:
|
|
1054
|
+
compiler = BytecodeCompiler(optimize=True)
|
|
1055
|
+
compiled_bytecode = compiler.compile(program)
|
|
1056
|
+
# Persist .zxc for next run
|
|
1057
|
+
if compiled_bytecode:
|
|
1058
|
+
try:
|
|
1059
|
+
from .binary_bytecode import save_zxc, zxc_path_for
|
|
1060
|
+
save_zxc(zxc_path_for(candidate), compiled_bytecode)
|
|
1061
|
+
except Exception:
|
|
1062
|
+
pass
|
|
1063
|
+
|
|
1064
|
+
if compiled_bytecode:
|
|
1065
|
+
# Execute module via VM (fast)
|
|
1066
|
+
vm_env = {k: v for k, v in module_env.store.items()}
|
|
1067
|
+
child_vm = VM.create_child(parent_vm=self, env=vm_env)
|
|
1068
|
+
result = child_vm._run_stack_bytecode_sync(compiled_bytecode, debug=False)
|
|
1069
|
+
|
|
1070
|
+
# Update module environment from VM execution
|
|
1071
|
+
for k, v in child_vm.env.items():
|
|
1072
|
+
module_env.set(k, v)
|
|
1073
|
+
|
|
1074
|
+
self._return_vm_to_pool(child_vm)
|
|
1075
|
+
|
|
1076
|
+
cache_module(normalized_path, module_env, compiled_bytecode, parsed_ast)
|
|
1077
|
+
cache_module(normalize_path(candidate), module_env, compiled_bytecode, parsed_ast)
|
|
1078
|
+
loaded = True
|
|
1079
|
+
break
|
|
1080
|
+
except Exception as e:
|
|
1081
|
+
if os.environ.get("ZEXUS_DEBUG_COMPILER"):
|
|
1082
|
+
print(f"[DEBUG] Compiler exception for {candidate}: {e}")
|
|
1083
|
+
pass
|
|
1084
|
+
|
|
1085
|
+
# Fallback to interpreter execution (slow path)
|
|
1086
|
+
if self._action_evaluator is None:
|
|
1087
|
+
self._action_evaluator = Evaluator(use_vm=False)
|
|
1088
|
+
self._action_evaluator.eval_node(program, module_env)
|
|
1089
|
+
cache_module(normalized_path, module_env, None, parsed_ast)
|
|
1090
|
+
cache_module(normalize_path(candidate), module_env, None, parsed_ast)
|
|
1091
|
+
loaded = True
|
|
1092
|
+
break
|
|
1093
|
+
except Exception:
|
|
1094
|
+
continue
|
|
1095
|
+
finally:
|
|
1096
|
+
end_loading(normalized_path)
|
|
1097
|
+
|
|
1098
|
+
if not loaded:
|
|
1099
|
+
try:
|
|
1100
|
+
invalidate_module(normalized_path)
|
|
1101
|
+
except Exception:
|
|
1102
|
+
pass
|
|
1103
|
+
return None
|
|
1104
|
+
return module_env
|
|
1105
|
+
|
|
1106
|
+
def _execute_import(self, module_path: str, alias: str = "", names: Optional[List[Any]] = None, is_named: bool = False, alias_map: Optional[Dict[str, str]] = None):
|
|
1107
|
+
if not module_path:
|
|
1108
|
+
return None
|
|
1109
|
+
names = names or []
|
|
1110
|
+
alias_map = alias_map or {}
|
|
1111
|
+
module_env = None
|
|
1112
|
+
module_map = None
|
|
1113
|
+
trace_imports = os.environ.get("ZEXUS_VM_IMPORT_TRACE")
|
|
1114
|
+
trace_enabled = trace_imports and trace_imports.lower() not in ("0", "false", "off")
|
|
1115
|
+
|
|
1116
|
+
try:
|
|
1117
|
+
from ..stdlib_integration import is_stdlib_module, get_stdlib_module
|
|
1118
|
+
from ..builtin_modules import is_builtin_module, get_builtin_module
|
|
1119
|
+
if is_stdlib_module(module_path):
|
|
1120
|
+
module_env = get_stdlib_module(module_path)
|
|
1121
|
+
elif is_builtin_module(module_path):
|
|
1122
|
+
module_env = get_builtin_module(module_path, None)
|
|
1123
|
+
except Exception:
|
|
1124
|
+
module_env = None
|
|
1125
|
+
|
|
1126
|
+
if module_env is None:
|
|
1127
|
+
module_env = self._load_zexus_module_env(module_path)
|
|
1128
|
+
if trace_enabled:
|
|
1129
|
+
status = "ok" if module_env is not None else "failed"
|
|
1130
|
+
print(f"[VM TRACE] import {module_path} -> {status}")
|
|
1131
|
+
|
|
1132
|
+
if module_env is not None:
|
|
1133
|
+
module_map = self._module_env_to_map(module_env) or {}
|
|
1134
|
+
if is_named and names:
|
|
1135
|
+
for raw in names:
|
|
1136
|
+
key = raw.value if hasattr(raw, "value") else str(raw)
|
|
1137
|
+
dest = alias_map.get(key, key)
|
|
1138
|
+
value = module_map.get(key)
|
|
1139
|
+
self.env[dest] = value
|
|
1140
|
+
self._bump_env_version(dest, value)
|
|
1141
|
+
elif alias:
|
|
1142
|
+
self.env[alias] = module_map
|
|
1143
|
+
self._bump_env_version(alias, module_map)
|
|
1144
|
+
else:
|
|
1145
|
+
for key, value in module_map.items():
|
|
1146
|
+
self.env[key] = value
|
|
1147
|
+
self._bump_env_version(key, value)
|
|
1148
|
+
return module_env
|
|
1149
|
+
|
|
1150
|
+
try:
|
|
1151
|
+
mod = importlib.import_module(module_path)
|
|
1152
|
+
key = alias or module_path
|
|
1153
|
+
self.env[key] = mod
|
|
1154
|
+
self._bump_env_version(key, mod)
|
|
1155
|
+
return mod
|
|
1156
|
+
except Exception:
|
|
1157
|
+
key = alias or module_path
|
|
1158
|
+
self.env[key] = None
|
|
1159
|
+
self._bump_env_version(key, None)
|
|
1160
|
+
return None
|
|
1161
|
+
|
|
1162
|
+
def _get_cached_method(self, target: Any, method_name: str):
|
|
1163
|
+
if target is None:
|
|
1164
|
+
return None
|
|
1165
|
+
if isinstance(target, (dict, ZMap, ZList)):
|
|
1166
|
+
return None
|
|
1167
|
+
try:
|
|
1168
|
+
if hasattr(target, "__dict__") and method_name in target.__dict__:
|
|
1169
|
+
return getattr(target, method_name, None)
|
|
1170
|
+
except Exception:
|
|
1171
|
+
return getattr(target, method_name, None)
|
|
1172
|
+
|
|
1173
|
+
key = (type(target), method_name)
|
|
1174
|
+
cached = self._method_cache.get(key)
|
|
1175
|
+
if cached is not None:
|
|
1176
|
+
try:
|
|
1177
|
+
return cached.__get__(target, type(target))
|
|
1178
|
+
except Exception:
|
|
1179
|
+
return getattr(target, method_name, None)
|
|
1180
|
+
|
|
1181
|
+
attr = getattr(type(target), method_name, None)
|
|
1182
|
+
if attr is not None:
|
|
1183
|
+
self._method_cache[key] = attr
|
|
1184
|
+
try:
|
|
1185
|
+
return attr.__get__(target, type(target))
|
|
1186
|
+
except Exception:
|
|
1187
|
+
return getattr(target, method_name, None)
|
|
1188
|
+
return getattr(target, method_name, None)
|
|
1189
|
+
|
|
547
1190
|
def execute(self, code: Union[List[Tuple], Any], debug: bool = False) -> Any:
|
|
548
1191
|
"""
|
|
549
1192
|
Execute code (High-level ops or Bytecode) using optimal execution mode.
|
|
@@ -551,6 +1194,7 @@ class VM:
|
|
|
551
1194
|
"""
|
|
552
1195
|
start_time = time.perf_counter()
|
|
553
1196
|
self._execution_count += 1
|
|
1197
|
+
self._in_execution = getattr(self, "_in_execution", 0) + 1
|
|
554
1198
|
|
|
555
1199
|
# Handle High-Level Ops (List format)
|
|
556
1200
|
if isinstance(code, list) and not hasattr(code, "instructions"):
|
|
@@ -558,7 +1202,7 @@ class VM:
|
|
|
558
1202
|
print("[VM] Executing High-Level Ops")
|
|
559
1203
|
try:
|
|
560
1204
|
# Run purely async internally, execute blocks
|
|
561
|
-
return
|
|
1205
|
+
return self._run_coroutine_sync(self._run_high_level_ops(code, debug or self.debug))
|
|
562
1206
|
except Exception as e:
|
|
563
1207
|
if debug or self.debug: print(f"[VM HL Error] {e}")
|
|
564
1208
|
raise e
|
|
@@ -567,6 +1211,10 @@ class VM:
|
|
|
567
1211
|
try:
|
|
568
1212
|
execution_mode = self._select_execution_mode(code)
|
|
569
1213
|
self._mode_usage[execution_mode.value] += 1
|
|
1214
|
+
|
|
1215
|
+
trace_mode = os.environ.get("ZEXUS_VM_TRACE_MODE")
|
|
1216
|
+
if trace_mode and trace_mode.lower() not in ("0", "false", "off"):
|
|
1217
|
+
print(f"[VM TRACE] execution mode={execution_mode.value}")
|
|
570
1218
|
|
|
571
1219
|
if debug or self.debug:
|
|
572
1220
|
print(f"[VM] Executing Bytecode | Mode: {execution_mode.value}")
|
|
@@ -579,24 +1227,50 @@ class VM:
|
|
|
579
1227
|
elif execution_mode == VMMode.PARALLEL and self._parallel_vm:
|
|
580
1228
|
result = self._execute_parallel(code, debug)
|
|
581
1229
|
|
|
582
|
-
# 3.
|
|
1230
|
+
# 3. Fast synchronous path for performance mode (no async overhead)
|
|
1231
|
+
elif getattr(self, '_perf_fast_dispatch', False):
|
|
1232
|
+
result = self._run_stack_bytecode_sync(code, debug)
|
|
1233
|
+
|
|
1234
|
+
# 4. Stack Mode (Standard/Fallback + Async Support)
|
|
583
1235
|
else:
|
|
584
|
-
result =
|
|
1236
|
+
result = self._run_coroutine_sync(self._execute_stack(code, debug))
|
|
585
1237
|
|
|
586
1238
|
# JIT Tracking
|
|
587
1239
|
if self.use_jit and hasattr(code, 'instructions'):
|
|
588
1240
|
execution_time = time.perf_counter() - start_time
|
|
589
1241
|
self._track_execution_for_jit(code, execution_time, execution_mode)
|
|
590
1242
|
|
|
1243
|
+
profile_print = os.environ.get("ZEXUS_VM_PROFILE_PRINT")
|
|
1244
|
+
if profile_print and profile_print.lower() not in ("0", "false", "off"):
|
|
1245
|
+
if self._last_opcode_profile:
|
|
1246
|
+
try:
|
|
1247
|
+
top_n = int(os.environ.get("ZEXUS_VM_PROFILE_TOP", "10"))
|
|
1248
|
+
except Exception:
|
|
1249
|
+
top_n = 10
|
|
1250
|
+
total_ops = sum(count for _, count in self._last_opcode_profile)
|
|
1251
|
+
elapsed = time.perf_counter() - start_time
|
|
1252
|
+
ops_per_sec = (total_ops / elapsed) if elapsed > 0 else 0.0
|
|
1253
|
+
print(f"[VM PROFILE] total_ops={total_ops} top={top_n} elapsed_ms={elapsed * 1000:.2f} ops_per_sec={ops_per_sec:.2f}")
|
|
1254
|
+
for op_name, count in self._last_opcode_profile[:top_n]:
|
|
1255
|
+
pct = (count / total_ops * 100) if total_ops else 0.0
|
|
1256
|
+
print(f"[VM PROFILE] {op_name} count={count} pct={pct:.2f}%")
|
|
591
1257
|
return result
|
|
592
1258
|
|
|
593
1259
|
finally:
|
|
1260
|
+
self._in_execution = max(0, getattr(self, "_in_execution", 1) - 1)
|
|
594
1261
|
self._total_execution_time += (time.perf_counter() - start_time)
|
|
595
1262
|
|
|
596
1263
|
def _select_execution_mode(self, code) -> VMMode:
|
|
597
1264
|
if self.mode != VMMode.AUTO:
|
|
598
1265
|
return self.mode
|
|
599
|
-
|
|
1266
|
+
|
|
1267
|
+
if hasattr(code, 'instructions'):
|
|
1268
|
+
instructions = code.instructions
|
|
1269
|
+
if self.prefer_parallel and self._parallel_vm and self._is_parallelizable(instructions):
|
|
1270
|
+
return VMMode.PARALLEL
|
|
1271
|
+
if self.prefer_register and self._register_vm and self._is_register_friendly(instructions):
|
|
1272
|
+
return VMMode.REGISTER
|
|
1273
|
+
|
|
600
1274
|
if self.use_jit:
|
|
601
1275
|
return VMMode.STACK
|
|
602
1276
|
|
|
@@ -606,7 +1280,7 @@ class VM:
|
|
|
606
1280
|
return VMMode.PARALLEL
|
|
607
1281
|
if self._register_vm and self._is_register_friendly(instructions):
|
|
608
1282
|
return VMMode.REGISTER
|
|
609
|
-
|
|
1283
|
+
|
|
610
1284
|
return VMMode.STACK
|
|
611
1285
|
|
|
612
1286
|
# ==================== Specialized Execution Methods ====================
|
|
@@ -620,6 +1294,17 @@ class VM:
|
|
|
620
1294
|
def _execute_register(self, bytecode, debug: bool = False):
|
|
621
1295
|
"""Execute using register-based VM"""
|
|
622
1296
|
try:
|
|
1297
|
+
if self.enable_bytecode_converter and self.bytecode_converter and hasattr(bytecode, "instructions"):
|
|
1298
|
+
try:
|
|
1299
|
+
if not bytecode.metadata.get("converted_to_register"):
|
|
1300
|
+
bytecode = self.bytecode_converter.convert(bytecode)
|
|
1301
|
+
except Exception:
|
|
1302
|
+
pass
|
|
1303
|
+
if self.enable_register_allocation and self.register_allocator and hasattr(bytecode, "instructions"):
|
|
1304
|
+
try:
|
|
1305
|
+
self._last_register_allocation = self.allocate_registers(bytecode.instructions)
|
|
1306
|
+
except Exception:
|
|
1307
|
+
self._last_register_allocation = None
|
|
623
1308
|
# Ensure register VM has current environment and builtins
|
|
624
1309
|
self._register_vm.env = self.env.copy()
|
|
625
1310
|
self._register_vm.builtins = self.builtins.copy()
|
|
@@ -634,34 +1319,126 @@ class VM:
|
|
|
634
1319
|
return result
|
|
635
1320
|
except Exception as e:
|
|
636
1321
|
if debug: print(f"[VM Register] Failed: {e}, falling back to stack")
|
|
637
|
-
return
|
|
1322
|
+
return self._run_coroutine_sync(self._run_stack_bytecode(bytecode, debug))
|
|
638
1323
|
|
|
639
1324
|
def _execute_parallel(self, bytecode, debug: bool = False):
|
|
640
1325
|
"""Execute using parallel VM"""
|
|
641
1326
|
try:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1327
|
+
optimized_bytecode = self._optimize_bytecode_for_parallel(bytecode)
|
|
1328
|
+
return self._parallel_vm.execute(
|
|
1329
|
+
optimized_bytecode,
|
|
1330
|
+
initial_state={
|
|
1331
|
+
"env": self.env.copy(),
|
|
1332
|
+
"builtins": self.builtins.copy(),
|
|
1333
|
+
"parent_env": self._parent_env,
|
|
1334
|
+
},
|
|
645
1335
|
)
|
|
646
1336
|
except Exception as e:
|
|
647
1337
|
if debug: print(f"[VM Parallel] Failed: {e}, falling back to stack")
|
|
648
|
-
return
|
|
1338
|
+
return self._run_coroutine_sync(self._run_stack_bytecode(bytecode, debug))
|
|
1339
|
+
|
|
1340
|
+
def _optimize_bytecode_for_parallel(self, bytecode):
|
|
1341
|
+
"""Apply peephole/SSA optimizations and map opcodes for parallel execution."""
|
|
1342
|
+
from .bytecode import Bytecode, Opcode
|
|
1343
|
+
|
|
1344
|
+
consts = list(getattr(bytecode, "constants", []))
|
|
1345
|
+
instrs = list(getattr(bytecode, "instructions", []))
|
|
1346
|
+
|
|
1347
|
+
if self.enable_bytecode_optimizer and self.bytecode_optimizer:
|
|
1348
|
+
try:
|
|
1349
|
+
normalized_for_opt: List[Tuple[Any, Any]] = []
|
|
1350
|
+
for instr in instrs:
|
|
1351
|
+
if instr is None:
|
|
1352
|
+
continue
|
|
1353
|
+
if isinstance(instr, tuple) and len(instr) >= 2:
|
|
1354
|
+
op = instr[0]
|
|
1355
|
+
operand = instr[1] if len(instr) == 2 else tuple(instr[1:])
|
|
1356
|
+
op_name = op.name if hasattr(op, "name") else op
|
|
1357
|
+
normalized_for_opt.append((str(op_name), operand))
|
|
1358
|
+
instrs = self.bytecode_optimizer.optimize(normalized_for_opt, consts)
|
|
1359
|
+
except Exception:
|
|
1360
|
+
pass
|
|
1361
|
+
|
|
1362
|
+
if self.enable_peephole_optimizer and self.peephole_optimizer:
|
|
1363
|
+
try:
|
|
1364
|
+
instrs, consts = self.peephole_optimizer.optimize_bytecode(instrs, consts)
|
|
1365
|
+
except Exception:
|
|
1366
|
+
pass
|
|
1367
|
+
|
|
1368
|
+
normalized: List[Tuple[Any, Any]] = []
|
|
1369
|
+
for instr in instrs:
|
|
1370
|
+
if instr is None:
|
|
1371
|
+
continue
|
|
1372
|
+
if isinstance(instr, tuple) and len(instr) >= 2:
|
|
1373
|
+
op = instr[0]
|
|
1374
|
+
operand = instr[1] if len(instr) == 2 else tuple(instr[1:])
|
|
1375
|
+
op_name = op.name if hasattr(op, "name") else op
|
|
1376
|
+
normalized.append((op_name, operand))
|
|
1377
|
+
|
|
1378
|
+
instrs = normalized
|
|
1379
|
+
|
|
1380
|
+
if self.enable_ssa and self.ssa_converter:
|
|
1381
|
+
try:
|
|
1382
|
+
ssa_program = self.ssa_converter.convert_to_ssa(instrs)
|
|
1383
|
+
ssa_instrs = destruct_ssa(ssa_program)
|
|
1384
|
+
instrs, consts = self._normalize_ssa_instructions(ssa_instrs, consts)
|
|
1385
|
+
except Exception:
|
|
1386
|
+
pass
|
|
1387
|
+
|
|
1388
|
+
mapped: List[Tuple[Any, Any]] = []
|
|
1389
|
+
for op, operand in instrs:
|
|
1390
|
+
if isinstance(op, str) and op in Opcode.__members__:
|
|
1391
|
+
mapped.append((Opcode[op], operand))
|
|
1392
|
+
else:
|
|
1393
|
+
mapped.append((op, operand))
|
|
1394
|
+
|
|
1395
|
+
return Bytecode(instructions=mapped, constants=consts)
|
|
649
1396
|
|
|
650
1397
|
# ==================== JIT & Optimization Heuristics ====================
|
|
651
1398
|
|
|
652
1399
|
def _is_parallelizable(self, instructions) -> bool:
|
|
653
|
-
if len(instructions) < 100:
|
|
654
|
-
|
|
1400
|
+
if len(instructions) < 100:
|
|
1401
|
+
return False
|
|
1402
|
+
def _op_name(op):
|
|
1403
|
+
return op.name if hasattr(op, 'name') else op
|
|
1404
|
+
independent_ops = sum(
|
|
1405
|
+
1 for op, _ in instructions
|
|
1406
|
+
if _op_name(op) in ['LOAD_CONST', 'ADD', 'SUB', 'MUL', 'HASH_BLOCK']
|
|
1407
|
+
)
|
|
655
1408
|
return independent_ops / len(instructions) > 0.3
|
|
656
1409
|
|
|
657
1410
|
def _is_register_friendly(self, instructions) -> bool:
|
|
658
|
-
|
|
1411
|
+
def _op_name(op):
|
|
1412
|
+
return op.name if hasattr(op, 'name') else op
|
|
1413
|
+
arith_ops = sum(
|
|
1414
|
+
1 for op, _ in instructions
|
|
1415
|
+
if _op_name(op) in ['ADD', 'SUB', 'MUL', 'DIV', 'EQ', 'LT']
|
|
1416
|
+
)
|
|
659
1417
|
return arith_ops / max(len(instructions), 1) > 0.4
|
|
660
1418
|
|
|
661
1419
|
def _track_execution_for_jit(self, bytecode, execution_time: float, execution_mode: VMMode):
|
|
662
1420
|
if not self.use_jit or not self.jit_compiler: return
|
|
1421
|
+
|
|
1422
|
+
if (not self._native_jit_auto_enabled
|
|
1423
|
+
and self._opcode_exec_count >= self._native_jit_auto_threshold):
|
|
1424
|
+
self._native_jit_auto_enabled = self.jit_compiler.enable_native_backend()
|
|
663
1425
|
|
|
664
|
-
|
|
1426
|
+
# OPTIMIZATION: Skip lock for single-threaded execution (47 lock acquisitions cost 37.6s!)
|
|
1427
|
+
use_lock = self._jit_lock is not None
|
|
1428
|
+
|
|
1429
|
+
if use_lock:
|
|
1430
|
+
with self._jit_lock:
|
|
1431
|
+
hot_path_info = self.jit_compiler.track_execution(bytecode, execution_time)
|
|
1432
|
+
bytecode_hash = getattr(hot_path_info, 'bytecode_hash', None) or self.jit_compiler._hash_bytecode(bytecode)
|
|
1433
|
+
|
|
1434
|
+
if bytecode_hash not in self._jit_execution_stats:
|
|
1435
|
+
self._jit_execution_stats[bytecode_hash] = []
|
|
1436
|
+
self._jit_execution_stats[bytecode_hash].append(execution_time)
|
|
1437
|
+
|
|
1438
|
+
# Check if should compile (outside lock to avoid holding during compilation)
|
|
1439
|
+
should_compile = self.jit_compiler.should_compile(bytecode_hash)
|
|
1440
|
+
else:
|
|
1441
|
+
# Lock-free path for single-threaded execution
|
|
665
1442
|
hot_path_info = self.jit_compiler.track_execution(bytecode, execution_time)
|
|
666
1443
|
bytecode_hash = getattr(hot_path_info, 'bytecode_hash', None) or self.jit_compiler._hash_bytecode(bytecode)
|
|
667
1444
|
|
|
@@ -669,17 +1446,86 @@ class VM:
|
|
|
669
1446
|
self._jit_execution_stats[bytecode_hash] = []
|
|
670
1447
|
self._jit_execution_stats[bytecode_hash].append(execution_time)
|
|
671
1448
|
|
|
672
|
-
# Check if should compile (outside lock to avoid holding during compilation)
|
|
673
1449
|
should_compile = self.jit_compiler.should_compile(bytecode_hash)
|
|
674
1450
|
|
|
675
1451
|
# Compile outside the lock to prevent blocking other executions
|
|
676
1452
|
if should_compile:
|
|
677
1453
|
if self.debug: print(f"[VM JIT] Compiling hot path: {bytecode_hash[:8]}")
|
|
678
|
-
|
|
679
|
-
|
|
1454
|
+
if use_lock:
|
|
1455
|
+
with self._jit_lock:
|
|
1456
|
+
# Double-check it hasn't been compiled by another thread
|
|
1457
|
+
if self.jit_compiler.should_compile(bytecode_hash):
|
|
1458
|
+
self.jit_compiler.compile_hot_path(bytecode)
|
|
1459
|
+
else:
|
|
680
1460
|
if self.jit_compiler.should_compile(bytecode_hash):
|
|
681
1461
|
self.jit_compiler.compile_hot_path(bytecode)
|
|
682
1462
|
|
|
1463
|
+
def _normalize_ssa_instructions(self, instructions: List[Tuple], consts: List[Any]) -> Tuple[List[Tuple], List[Any]]:
|
|
1464
|
+
"""Normalize SSA-destructed instructions to (opcode, operand) format."""
|
|
1465
|
+
# Large programs can produce large instruction streams. The previous
|
|
1466
|
+
# constant lookup did a linear scan for every constant insertion, which
|
|
1467
|
+
# becomes O(n^2) on big constant pools. Prefer a dict for hashable
|
|
1468
|
+
# constants; fall back to linear scan for unhashables.
|
|
1469
|
+
const_index: Dict[Tuple[type, Any], int] = {}
|
|
1470
|
+
try:
|
|
1471
|
+
for i, const in enumerate(consts):
|
|
1472
|
+
try:
|
|
1473
|
+
const_index[(type(const), const)] = i
|
|
1474
|
+
except Exception:
|
|
1475
|
+
continue
|
|
1476
|
+
except Exception:
|
|
1477
|
+
const_index = {}
|
|
1478
|
+
|
|
1479
|
+
def _const_index(value: Any) -> int:
|
|
1480
|
+
try:
|
|
1481
|
+
key = (type(value), value)
|
|
1482
|
+
existing = const_index.get(key)
|
|
1483
|
+
if existing is not None:
|
|
1484
|
+
return existing
|
|
1485
|
+
except Exception:
|
|
1486
|
+
key = None
|
|
1487
|
+
|
|
1488
|
+
# Fallback for unhashable values: preserve legacy semantics
|
|
1489
|
+
for i, const in enumerate(consts):
|
|
1490
|
+
try:
|
|
1491
|
+
if const == value and type(const) == type(value):
|
|
1492
|
+
return i
|
|
1493
|
+
except Exception:
|
|
1494
|
+
continue
|
|
1495
|
+
|
|
1496
|
+
consts.append(value)
|
|
1497
|
+
idx = len(consts) - 1
|
|
1498
|
+
if key is not None:
|
|
1499
|
+
try:
|
|
1500
|
+
const_index[key] = idx
|
|
1501
|
+
except Exception:
|
|
1502
|
+
pass
|
|
1503
|
+
return idx
|
|
1504
|
+
|
|
1505
|
+
normalized: List[Tuple] = []
|
|
1506
|
+
for instr in instructions:
|
|
1507
|
+
if instr is None:
|
|
1508
|
+
continue
|
|
1509
|
+
if not isinstance(instr, tuple):
|
|
1510
|
+
continue
|
|
1511
|
+
op = instr[0]
|
|
1512
|
+
if len(instr) == 2:
|
|
1513
|
+
normalized.append((op, instr[1]))
|
|
1514
|
+
continue
|
|
1515
|
+
if op == "MOVE" and len(instr) >= 3:
|
|
1516
|
+
src = instr[1]
|
|
1517
|
+
dest = instr[2]
|
|
1518
|
+
src_idx = _const_index(src)
|
|
1519
|
+
dest_idx = _const_index(dest)
|
|
1520
|
+
normalized.append(("LOAD_NAME", src_idx))
|
|
1521
|
+
normalized.append(("STORE_NAME", dest_idx))
|
|
1522
|
+
continue
|
|
1523
|
+
|
|
1524
|
+
operand = tuple(instr[1:])
|
|
1525
|
+
normalized.append((op, operand))
|
|
1526
|
+
|
|
1527
|
+
return normalized, consts
|
|
1528
|
+
|
|
683
1529
|
def get_jit_stats(self) -> Dict[str, Any]:
|
|
684
1530
|
if self.use_jit and self.jit_compiler:
|
|
685
1531
|
stats = self.jit_compiler.get_stats()
|
|
@@ -844,11 +1690,7 @@ class VM:
|
|
|
844
1690
|
await self._call_builtin_async(h, [payload])
|
|
845
1691
|
elif code == "IMPORT":
|
|
846
1692
|
_, module_path, alias = op
|
|
847
|
-
|
|
848
|
-
mod = importlib.import_module(module_path)
|
|
849
|
-
self.env[alias or module_path] = mod
|
|
850
|
-
except Exception:
|
|
851
|
-
self.env[alias or module_path] = None
|
|
1693
|
+
self._execute_import(module_path, alias=alias or "")
|
|
852
1694
|
elif code == "DEFINE_ENUM":
|
|
853
1695
|
_, name, members = op
|
|
854
1696
|
enum_registry = self.env.setdefault("enums", {})
|
|
@@ -892,66 +1734,1068 @@ class VM:
|
|
|
892
1734
|
if tag == "LIST": return [self._eval_hl_op(e) for e in op[1]]
|
|
893
1735
|
return None
|
|
894
1736
|
|
|
895
|
-
# ====================
|
|
1737
|
+
# ==================== Rust VM Adaptive Routing (Phase 3) ====================
|
|
896
1738
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1739
|
+
def _execute_via_rust_vm(self, bytecode, debug=False):
|
|
1740
|
+
"""Serialize bytecode to .zxc and execute via the Rust VM.
|
|
1741
|
+
|
|
1742
|
+
Returns the result value on success, or ``_RUST_VM_FALLBACK_SENTINEL``
|
|
1743
|
+
if the Rust VM signals it needs a Python fallback (e.g. for
|
|
1744
|
+
CALL_NAME / CALL_METHOD that need Python interop).
|
|
1745
|
+
"""
|
|
1746
|
+
from .binary_bytecode import serialize as _serialize_zxc
|
|
1747
|
+
|
|
1748
|
+
# Serialize bytecode → .zxc binary
|
|
1749
|
+
zxc_data = _serialize_zxc(bytecode, include_checksum=True)
|
|
1750
|
+
|
|
1751
|
+
# Build env dict for Rust (only simple serializable values)
|
|
1752
|
+
rust_env = {}
|
|
1753
|
+
for k, v in self.env.items():
|
|
1754
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
1755
|
+
rust_env[k] = v
|
|
1756
|
+
elif isinstance(v, ZInteger):
|
|
1757
|
+
rust_env[k] = v.value
|
|
1758
|
+
elif isinstance(v, ZFloat):
|
|
1759
|
+
rust_env[k] = v.value
|
|
1760
|
+
elif isinstance(v, ZString):
|
|
1761
|
+
rust_env[k] = v.value
|
|
1762
|
+
elif isinstance(v, ZBoolean):
|
|
1763
|
+
rust_env[k] = v.value
|
|
1764
|
+
elif isinstance(v, (ZNull, type(None))):
|
|
1765
|
+
rust_env[k] = None
|
|
1766
|
+
# Skip non-serializable values (callables, AST nodes, etc.)
|
|
1767
|
+
|
|
1768
|
+
# Build state dict (if blockchain state exists)
|
|
1769
|
+
rust_state = {}
|
|
1770
|
+
bc_state = self.env.get("_blockchain_state")
|
|
1771
|
+
if bc_state and isinstance(bc_state, dict):
|
|
1772
|
+
for k, v in bc_state.items():
|
|
1773
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
1774
|
+
rust_state[k] = v
|
|
1775
|
+
|
|
1776
|
+
# Gas limit
|
|
1777
|
+
gas_limit = 0
|
|
1778
|
+
if self.gas_metering:
|
|
1779
|
+
remaining_fn = getattr(self.gas_metering, "remaining", None)
|
|
1780
|
+
if callable(remaining_fn):
|
|
905
1781
|
try:
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if
|
|
1782
|
+
rem = remaining_fn()
|
|
1783
|
+
if isinstance(rem, (int, float)) and rem > 0:
|
|
1784
|
+
gas_limit = int(rem)
|
|
1785
|
+
except Exception:
|
|
1786
|
+
pass
|
|
1787
|
+
if gas_limit == 0:
|
|
1788
|
+
gas_limit_val = getattr(self.gas_metering, "gas_limit", 0) or 0
|
|
1789
|
+
gas_used_val = getattr(self.gas_metering, "gas_used", 0) or 0
|
|
1790
|
+
if isinstance(gas_limit_val, (int, float)) and isinstance(gas_used_val, (int, float)):
|
|
1791
|
+
if gas_limit_val > gas_used_val:
|
|
1792
|
+
gas_limit = int(gas_limit_val - gas_used_val)
|
|
1793
|
+
|
|
1794
|
+
# Execute via Rust VM
|
|
1795
|
+
result_dict = self._rust_vm_executor.execute(
|
|
1796
|
+
zxc_data,
|
|
1797
|
+
env=rust_env or None,
|
|
1798
|
+
state=rust_state or None,
|
|
1799
|
+
gas_limit=gas_limit,
|
|
1800
|
+
)
|
|
1801
|
+
|
|
1802
|
+
# Check for fallback
|
|
1803
|
+
if result_dict.get("needs_fallback", False):
|
|
1804
|
+
self._rust_vm_stats["rust_fallbacks"] += 1
|
|
1805
|
+
if debug:
|
|
1806
|
+
print("[VM] Rust VM needs Python fallback — delegating to Python VM")
|
|
1807
|
+
return _RUST_VM_FALLBACK_SENTINEL
|
|
1808
|
+
|
|
1809
|
+
# Check for errors
|
|
1810
|
+
error = result_dict.get("error")
|
|
1811
|
+
if error:
|
|
1812
|
+
if "OutOfGas" in str(error):
|
|
1813
|
+
if self.gas_metering:
|
|
1814
|
+
raise OutOfGasError(str(error))
|
|
1815
|
+
raise RuntimeError(str(error))
|
|
1816
|
+
if "RequireFailed" in str(error):
|
|
1817
|
+
raise RuntimeError(str(error))
|
|
1818
|
+
raise RuntimeError(f"Rust VM error: {error}")
|
|
1819
|
+
|
|
1820
|
+
# Success — update stats
|
|
1821
|
+
self._rust_vm_stats["rust_executions"] += 1
|
|
1822
|
+
self._rust_vm_stats["total_rust_ops"] += result_dict.get("instructions_executed", 0)
|
|
1823
|
+
|
|
1824
|
+
# Bridge gas usage back to Python metering
|
|
1825
|
+
if self.gas_metering:
|
|
1826
|
+
rust_gas = result_dict.get("gas_used", 0)
|
|
1827
|
+
if rust_gas > 0:
|
|
1828
|
+
gas_used_attr = getattr(self.gas_metering, "gas_used", None)
|
|
1829
|
+
if gas_used_attr is not None:
|
|
1830
|
+
self.gas_metering.gas_used = gas_used_attr + rust_gas
|
|
1831
|
+
add_gas = getattr(self.gas_metering, "add_gas", None)
|
|
1832
|
+
if add_gas:
|
|
1833
|
+
add_gas(rust_gas)
|
|
1834
|
+
|
|
1835
|
+
# Merge Rust env back into Python env
|
|
1836
|
+
rust_env_out = result_dict.get("env", {})
|
|
1837
|
+
if rust_env_out:
|
|
1838
|
+
for k, v in rust_env_out.items():
|
|
1839
|
+
self.env[k] = v
|
|
1840
|
+
|
|
1841
|
+
# Merge blockchain state back
|
|
1842
|
+
rust_state_out = result_dict.get("state", {})
|
|
1843
|
+
if rust_state_out and bc_state is not None:
|
|
1844
|
+
for k, v in rust_state_out.items():
|
|
1845
|
+
bc_state[k] = v
|
|
916
1846
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
ip = 0
|
|
921
|
-
running = True
|
|
922
|
-
return_value = None
|
|
923
|
-
profile_flag = os.environ.get("ZEXUS_VM_PROFILE_OPS")
|
|
924
|
-
profile_ops = profile_flag is not None and profile_flag.lower() not in ("0", "false", "off")
|
|
925
|
-
profile_verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
|
|
926
|
-
profile_verbose = profile_verbose_flag and profile_verbose_flag.lower() not in ("0", "false", "off")
|
|
927
|
-
opcode_counts: Optional[Dict[str, int]] = {} if profile_ops else None
|
|
928
|
-
if profile_ops and profile_verbose:
|
|
929
|
-
print(f"[VM DEBUG] opcode profiling enabled; instrs={len(instrs)}")
|
|
1847
|
+
if debug:
|
|
1848
|
+
print(f"[VM] Rust VM executed: ops={result_dict.get('instructions_executed', 0)} "
|
|
1849
|
+
f"gas={result_dict.get('gas_used', 0)}")
|
|
930
1850
|
|
|
931
|
-
|
|
932
|
-
__slots__ = ("data", "sp")
|
|
1851
|
+
return result_dict.get("result")
|
|
933
1852
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
self.sp = 0
|
|
1853
|
+
def get_rust_vm_stats(self):
|
|
1854
|
+
"""Return statistics about Rust VM usage."""
|
|
1855
|
+
return dict(self._rust_vm_stats)
|
|
938
1856
|
|
|
939
|
-
|
|
940
|
-
if self.sp >= len(self.data):
|
|
941
|
-
self.data.extend([None] * len(self.data))
|
|
1857
|
+
# ==================== Fast Synchronous Dispatch (Performance Mode) ====================
|
|
942
1858
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1859
|
+
def _run_stack_bytecode_sync(self, bytecode, debug=False):
|
|
1860
|
+
"""Synchronous fast-path execution without async overhead or gas metering."""
|
|
1861
|
+
consts = list(getattr(bytecode, "constants", []))
|
|
1862
|
+
instrs = list(getattr(bytecode, "instructions", []))
|
|
947
1863
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1864
|
+
if not self._native_jit_auto_enabled:
|
|
1865
|
+
self._opcode_exec_count += len(instrs)
|
|
1866
|
+
|
|
1867
|
+
# Normalize opcodes
|
|
1868
|
+
normalized: List[Tuple[str, Any]] = []
|
|
1869
|
+
for instr in instrs:
|
|
1870
|
+
if instr is None:
|
|
1871
|
+
continue
|
|
1872
|
+
if isinstance(instr, tuple) and len(instr) >= 2:
|
|
1873
|
+
op = instr[0]
|
|
1874
|
+
operand = instr[1] if len(instr) == 2 else tuple(instr[1:])
|
|
1875
|
+
op_name = op.name if hasattr(op, "name") else op
|
|
1876
|
+
normalized.append((op_name, operand))
|
|
1877
|
+
instrs = normalized
|
|
1878
|
+
|
|
1879
|
+
# Cython fast-path if available (skip when gas metering is active
|
|
1880
|
+
# because the native code doesn't enforce gas limits)
|
|
1881
|
+
if _FASTOPS_AVAILABLE and not self.gas_metering:
|
|
1882
|
+
try:
|
|
1883
|
+
return _fastops.execute(instrs, consts, self.env, self.builtins, self._closure_cells)
|
|
1884
|
+
except NotImplementedError:
|
|
1885
|
+
pass
|
|
1886
|
+
except Exception:
|
|
1887
|
+
pass
|
|
1888
|
+
|
|
1889
|
+
# Rust VM adaptive routing (Phase 3) — delegate to Rust when
|
|
1890
|
+
# the program is large enough to amortise serialisation overhead.
|
|
1891
|
+
if (self._rust_vm_enabled
|
|
1892
|
+
and self._rust_vm_executor is not None
|
|
1893
|
+
and len(instrs) >= self._rust_vm_threshold):
|
|
1894
|
+
try:
|
|
1895
|
+
rust_result = self._execute_via_rust_vm(bytecode, debug)
|
|
1896
|
+
if rust_result is not _RUST_VM_FALLBACK_SENTINEL:
|
|
1897
|
+
return rust_result
|
|
1898
|
+
# Rust signalled needs_fallback — continue to Python VM
|
|
1899
|
+
except Exception:
|
|
1900
|
+
self._rust_vm_stats["rust_fallbacks"] += 1
|
|
1901
|
+
|
|
1902
|
+
# Fast stack implementation
|
|
1903
|
+
stack: List[Any] = []
|
|
1904
|
+
stack_append = stack.append
|
|
1905
|
+
# stack_pop = stack.pop
|
|
1906
|
+
def stack_pop():
|
|
1907
|
+
if not stack:
|
|
1908
|
+
return None
|
|
1909
|
+
return stack.pop()
|
|
1910
|
+
|
|
1911
|
+
ip = 0
|
|
1912
|
+
trace_interval = 0
|
|
1913
|
+
try:
|
|
1914
|
+
trace_interval = int(os.environ.get("ZEXUS_VM_TRACE_INTERVAL", "0"))
|
|
1915
|
+
except Exception:
|
|
1916
|
+
trace_interval = 0
|
|
1917
|
+
trace_counter = 0
|
|
1918
|
+
instr_count = len(instrs)
|
|
1919
|
+
env = self.env
|
|
1920
|
+
builtins = self.builtins
|
|
1921
|
+
|
|
1922
|
+
def const(idx):
|
|
1923
|
+
return consts[idx] if isinstance(idx, int) and 0 <= idx < len(consts) else idx
|
|
1924
|
+
|
|
1925
|
+
def resolve(name):
|
|
1926
|
+
cached = self._name_cache.get(name)
|
|
1927
|
+
if cached and cached[1] == self._env_version:
|
|
1928
|
+
return cached[0]
|
|
1929
|
+
if name in env:
|
|
1930
|
+
val = env[name]
|
|
1931
|
+
resolved = val.value if isinstance(val, Cell) else val
|
|
1932
|
+
self._name_cache[name] = (resolved, self._env_version)
|
|
1933
|
+
return resolved
|
|
1934
|
+
if name in self._closure_cells:
|
|
1935
|
+
resolved = self._closure_cells[name].value
|
|
1936
|
+
self._name_cache[name] = (resolved, self._env_version)
|
|
1937
|
+
return resolved
|
|
1938
|
+
return None
|
|
1939
|
+
|
|
1940
|
+
def store(name, value):
|
|
1941
|
+
if name in env and isinstance(env[name], Cell):
|
|
1942
|
+
env[name].value = value
|
|
1943
|
+
self._bump_env_version(name, value)
|
|
1944
|
+
else:
|
|
1945
|
+
env[name] = value
|
|
1946
|
+
self._bump_env_version(name, value)
|
|
1947
|
+
|
|
1948
|
+
# Gas metering for sync path (security: prevents DoS via
|
|
1949
|
+
# unbounded computation even when using the fast path)
|
|
1950
|
+
_gas = self.gas_metering # may be None
|
|
1951
|
+
_gas_consume = _gas.consume if _gas else None
|
|
1952
|
+
_gas_light = self.enable_gas_light
|
|
1953
|
+
_gas_light_cost = self.gas_light_cost if _gas_light else 0
|
|
1954
|
+
_gas_consume_light = _gas.consume_light if _gas else None
|
|
1955
|
+
|
|
1956
|
+
while ip < instr_count:
|
|
1957
|
+
op_name, operand = instrs[ip]
|
|
1958
|
+
ip += 1
|
|
1959
|
+
|
|
1960
|
+
# --- Gas accounting (fast) ---
|
|
1961
|
+
if _gas is not None:
|
|
1962
|
+
if _gas_light:
|
|
1963
|
+
if not _gas_consume_light(_gas_light_cost):
|
|
1964
|
+
from .gas_metering import OutOfGasError
|
|
1965
|
+
raise OutOfGasError(_gas.gas_used, _gas.gas_limit, op_name)
|
|
1966
|
+
else:
|
|
1967
|
+
if not _gas_consume(op_name):
|
|
1968
|
+
from .gas_metering import OutOfGasError
|
|
1969
|
+
raise OutOfGasError(_gas.gas_used, _gas.gas_limit, op_name)
|
|
1970
|
+
|
|
1971
|
+
if trace_interval > 0:
|
|
1972
|
+
trace_counter += 1
|
|
1973
|
+
if trace_counter % trace_interval == 0:
|
|
1974
|
+
try:
|
|
1975
|
+
stack_size = len(stack)
|
|
1976
|
+
except Exception:
|
|
1977
|
+
stack_size = -1
|
|
1978
|
+
print(f"[VM TRACE] sync ip={ip} op={op_name} stack={stack_size}")
|
|
1979
|
+
|
|
1980
|
+
# Hot path: arithmetic and stack ops (inlined)
|
|
1981
|
+
if op_name == "LOAD_CONST":
|
|
1982
|
+
stack_append(const(operand))
|
|
1983
|
+
elif op_name == "LOAD_NAME":
|
|
1984
|
+
stack_append(resolve(const(operand)))
|
|
1985
|
+
elif op_name == "STORE_NAME":
|
|
1986
|
+
store(const(operand), stack_pop() if stack else None)
|
|
1987
|
+
elif op_name == "POP":
|
|
1988
|
+
if stack: stack_pop()
|
|
1989
|
+
elif op_name == "DUP":
|
|
1990
|
+
if stack: stack_append(stack[-1])
|
|
1991
|
+
elif op_name == "ADD":
|
|
1992
|
+
b = stack_pop() if stack else 0
|
|
1993
|
+
a = stack_pop() if stack else 0
|
|
1994
|
+
if hasattr(a, 'value'): a = a.value
|
|
1995
|
+
if hasattr(b, 'value'): b = b.value
|
|
1996
|
+
stack_append(a + b)
|
|
1997
|
+
elif op_name == "SUB":
|
|
1998
|
+
b = stack_pop() if stack else 0
|
|
1999
|
+
a = stack_pop() if stack else 0
|
|
2000
|
+
if hasattr(a, 'value'): a = a.value
|
|
2001
|
+
if hasattr(b, 'value'): b = b.value
|
|
2002
|
+
if a is None: a = 0
|
|
2003
|
+
if b is None: b = 0
|
|
2004
|
+
stack_append(a - b)
|
|
2005
|
+
elif op_name == "MUL":
|
|
2006
|
+
b = stack_pop() if stack else 0
|
|
2007
|
+
a = stack_pop() if stack else 0
|
|
2008
|
+
if hasattr(a, 'value'): a = a.value
|
|
2009
|
+
if hasattr(b, 'value'): b = b.value
|
|
2010
|
+
stack_append(a * b)
|
|
2011
|
+
elif op_name == "DIV":
|
|
2012
|
+
b = stack_pop() if stack else 1
|
|
2013
|
+
a = stack_pop() if stack else 0
|
|
2014
|
+
if hasattr(a, 'value'): a = a.value
|
|
2015
|
+
if hasattr(b, 'value'): b = b.value
|
|
2016
|
+
stack_append(a / b if b != 0 else 0)
|
|
2017
|
+
elif op_name == "MOD":
|
|
2018
|
+
b = stack_pop() if stack else 1
|
|
2019
|
+
a = stack_pop() if stack else 0
|
|
2020
|
+
stack_append(a % b if b != 0 else 0)
|
|
2021
|
+
elif op_name == "EQ":
|
|
2022
|
+
b = stack_pop() if stack else None
|
|
2023
|
+
a = stack_pop() if stack else None
|
|
2024
|
+
stack_append(a == b)
|
|
2025
|
+
elif op_name == "NEQ":
|
|
2026
|
+
b = stack_pop() if stack else None
|
|
2027
|
+
a = stack_pop() if stack else None
|
|
2028
|
+
stack_append(a != b)
|
|
2029
|
+
elif op_name == "LT":
|
|
2030
|
+
b = stack_pop() if stack else 0
|
|
2031
|
+
a = stack_pop() if stack else 0
|
|
2032
|
+
if a is None or b is None: stack_append(False)
|
|
2033
|
+
else: stack_append(a < b)
|
|
2034
|
+
elif op_name == "GT":
|
|
2035
|
+
b = stack_pop() if stack else 0
|
|
2036
|
+
a = stack_pop() if stack else 0
|
|
2037
|
+
if a is None or b is None: stack_append(False)
|
|
2038
|
+
else: stack_append(a > b)
|
|
2039
|
+
elif op_name == "LTE":
|
|
2040
|
+
b = stack_pop() if stack else 0
|
|
2041
|
+
a = stack_pop() if stack else 0
|
|
2042
|
+
if a is None or b is None: stack_append(False)
|
|
2043
|
+
else: stack_append(a <= b)
|
|
2044
|
+
elif op_name == "GTE":
|
|
2045
|
+
b = stack_pop() if stack else 0
|
|
2046
|
+
a = stack_pop() if stack else 0
|
|
2047
|
+
if a is None or b is None: stack_append(False)
|
|
2048
|
+
else: stack_append(a >= b)
|
|
2049
|
+
elif op_name == "NOT":
|
|
2050
|
+
a = stack_pop() if stack else False
|
|
2051
|
+
stack_append(not a)
|
|
2052
|
+
elif op_name == "NEG":
|
|
2053
|
+
a = stack_pop() if stack else 0
|
|
2054
|
+
stack_append(-a)
|
|
2055
|
+
elif op_name == "JUMP":
|
|
2056
|
+
ip = operand
|
|
2057
|
+
elif op_name == "JUMP_IF_FALSE":
|
|
2058
|
+
cond = stack_pop() if stack else None
|
|
2059
|
+
if not cond:
|
|
2060
|
+
ip = operand
|
|
2061
|
+
elif op_name == "RETURN":
|
|
2062
|
+
return stack_pop() if stack else None
|
|
2063
|
+
elif op_name == "BUILD_LIST":
|
|
2064
|
+
count = operand if operand is not None else 0
|
|
2065
|
+
elements = [stack_pop() for _ in range(count)][::-1]
|
|
2066
|
+
stack_append(elements)
|
|
2067
|
+
elif op_name == "BUILD_MAP":
|
|
2068
|
+
count = operand if operand is not None else 0
|
|
2069
|
+
result = {}
|
|
2070
|
+
for _ in range(count):
|
|
2071
|
+
val = stack_pop()
|
|
2072
|
+
key = stack_pop()
|
|
2073
|
+
result[key] = val
|
|
2074
|
+
stack_append(result)
|
|
2075
|
+
elif op_name == "INDEX":
|
|
2076
|
+
idx = stack_pop()
|
|
2077
|
+
obj = stack_pop()
|
|
2078
|
+
try:
|
|
2079
|
+
if isinstance(obj, ZList):
|
|
2080
|
+
stack_append(obj.get(idx))
|
|
2081
|
+
elif isinstance(obj, ZMap):
|
|
2082
|
+
stack_append(obj.get(idx))
|
|
2083
|
+
else:
|
|
2084
|
+
stack_append(obj[idx] if obj is not None else None)
|
|
2085
|
+
except (IndexError, KeyError, TypeError):
|
|
2086
|
+
stack_append(None)
|
|
2087
|
+
elif op_name == "SLICE":
|
|
2088
|
+
end = stack_pop() if stack else None
|
|
2089
|
+
start = stack_pop() if stack else None
|
|
2090
|
+
obj = stack_pop() if stack else None
|
|
2091
|
+
if hasattr(start, "value"):
|
|
2092
|
+
start = start.value
|
|
2093
|
+
if hasattr(end, "value"):
|
|
2094
|
+
end = end.value
|
|
2095
|
+
try:
|
|
2096
|
+
if isinstance(obj, ZList):
|
|
2097
|
+
stack_append(ZList(obj.elements[start:end]))
|
|
2098
|
+
elif isinstance(obj, ZString):
|
|
2099
|
+
stack_append(ZString(obj.value[start:end]))
|
|
2100
|
+
else:
|
|
2101
|
+
stack_append(obj[start:end] if obj is not None else None)
|
|
2102
|
+
except Exception:
|
|
2103
|
+
stack_append(None)
|
|
2104
|
+
elif op_name == "GET_LENGTH":
|
|
2105
|
+
obj = stack_pop()
|
|
2106
|
+
try:
|
|
2107
|
+
if obj is None:
|
|
2108
|
+
stack_append(0)
|
|
2109
|
+
elif isinstance(obj, ZList):
|
|
2110
|
+
stack_append(len(obj.elements))
|
|
2111
|
+
elif isinstance(obj, ZMap):
|
|
2112
|
+
stack_append(len(obj.pairs))
|
|
2113
|
+
elif hasattr(obj, '__len__'):
|
|
2114
|
+
stack_append(len(obj))
|
|
2115
|
+
else:
|
|
2116
|
+
stack_append(0)
|
|
2117
|
+
except Exception:
|
|
2118
|
+
stack_append(0)
|
|
2119
|
+
elif op_name == "CALL_NAME":
|
|
2120
|
+
name_idx, arg_count = operand
|
|
2121
|
+
func_name = const(name_idx)
|
|
2122
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
2123
|
+
fn = resolve(func_name) or builtins.get(func_name)
|
|
2124
|
+
if fn is None:
|
|
2125
|
+
res = self._call_fallback_builtin(func_name, args)
|
|
2126
|
+
else:
|
|
2127
|
+
res = self._invoke_callable_sync(fn, args)
|
|
2128
|
+
stack_append(res)
|
|
2129
|
+
elif op_name == "CALL_TOP":
|
|
2130
|
+
arg_count = operand or 0
|
|
2131
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
2132
|
+
fn_obj = stack_pop() if stack else None
|
|
2133
|
+
res = self._invoke_callable_sync(fn_obj, args)
|
|
2134
|
+
stack_append(res)
|
|
2135
|
+
elif op_name == "CALL_METHOD":
|
|
2136
|
+
if not operand:
|
|
2137
|
+
stack_append(None)
|
|
2138
|
+
continue
|
|
2139
|
+
method_idx, arg_count = operand
|
|
2140
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
2141
|
+
target = stack_pop() if stack else None
|
|
2142
|
+
method_name = const(method_idx)
|
|
2143
|
+
trace_calls = os.environ.get("ZEXUS_VM_TRACE_CALLS")
|
|
2144
|
+
if trace_calls:
|
|
2145
|
+
try:
|
|
2146
|
+
interval = int(trace_calls) if trace_calls.isdigit() else 1000
|
|
2147
|
+
except Exception:
|
|
2148
|
+
interval = 1000
|
|
2149
|
+
self._call_method_total += 1
|
|
2150
|
+
if interval > 0 and self._call_method_total % interval == 0:
|
|
2151
|
+
target_type = type(target).__name__ if target is not None else "None"
|
|
2152
|
+
print(f"[VM TRACE] CALL_METHOD total={self._call_method_total} method={method_name} target={target_type}")
|
|
2153
|
+
if target is None:
|
|
2154
|
+
stack_append(None)
|
|
2155
|
+
continue
|
|
2156
|
+
result = None
|
|
2157
|
+
try:
|
|
2158
|
+
if method_name == "set":
|
|
2159
|
+
if isinstance(target, ZMap) and len(args) >= 2:
|
|
2160
|
+
key = args[0]
|
|
2161
|
+
if isinstance(key, ZString):
|
|
2162
|
+
norm_key = key.value
|
|
2163
|
+
elif isinstance(key, str):
|
|
2164
|
+
norm_key = key
|
|
2165
|
+
elif hasattr(key, "inspect"):
|
|
2166
|
+
norm_key = key.inspect()
|
|
2167
|
+
else:
|
|
2168
|
+
norm_key = str(key)
|
|
2169
|
+
existing = target.pairs.get(norm_key)
|
|
2170
|
+
if existing is not None and existing.__class__.__name__ == 'SealedObject':
|
|
2171
|
+
raise ZEvaluationError(f"Cannot modify sealed map key: {key}")
|
|
2172
|
+
target.pairs[norm_key] = args[1]
|
|
2173
|
+
result = args[1]
|
|
2174
|
+
elif isinstance(target, ZList) and len(args) >= 2:
|
|
2175
|
+
target.set(args[0], args[1])
|
|
2176
|
+
result = args[1]
|
|
2177
|
+
elif isinstance(target, (dict, list)) and len(args) >= 2:
|
|
2178
|
+
target[args[0]] = args[1]
|
|
2179
|
+
result = args[1]
|
|
2180
|
+
elif method_name == "get":
|
|
2181
|
+
if isinstance(target, ZMap) and args:
|
|
2182
|
+
result = target.get(args[0])
|
|
2183
|
+
elif isinstance(target, dict) and args:
|
|
2184
|
+
result = target.get(args[0])
|
|
2185
|
+
elif hasattr(target, "call_method"):
|
|
2186
|
+
wrapped_args = [self._wrap_for_builtin(arg) for arg in args]
|
|
2187
|
+
try:
|
|
2188
|
+
from .. import security as _security
|
|
2189
|
+
_security._set_vm_action_context(True)
|
|
2190
|
+
except Exception:
|
|
2191
|
+
_security = None
|
|
2192
|
+
try:
|
|
2193
|
+
result = target.call_method(method_name, wrapped_args)
|
|
2194
|
+
finally:
|
|
2195
|
+
if _security is not None:
|
|
2196
|
+
try:
|
|
2197
|
+
_security._set_vm_action_context(False)
|
|
2198
|
+
except Exception:
|
|
2199
|
+
pass
|
|
2200
|
+
else:
|
|
2201
|
+
attr = self._get_cached_method(target, method_name)
|
|
2202
|
+
if callable(attr):
|
|
2203
|
+
result = attr(*args)
|
|
2204
|
+
elif isinstance(target, dict) and method_name in target:
|
|
2205
|
+
candidate = target[method_name]
|
|
2206
|
+
result = candidate(*args) if callable(candidate) else candidate
|
|
2207
|
+
else:
|
|
2208
|
+
result = attr
|
|
2209
|
+
except Exception:
|
|
2210
|
+
result = None
|
|
2211
|
+
stack_append(self._unwrap_after_builtin(result))
|
|
2212
|
+
elif op_name == "PRINT":
|
|
2213
|
+
val = stack_pop() if stack else None
|
|
2214
|
+
print(self._format_print_value(val))
|
|
2215
|
+
elif op_name == "GET_ATTR":
|
|
2216
|
+
attr = stack_pop() if stack else None
|
|
2217
|
+
obj = stack_pop() if stack else None
|
|
2218
|
+
if obj is None:
|
|
2219
|
+
stack_append(None)
|
|
2220
|
+
else:
|
|
2221
|
+
attr_name = attr.value if hasattr(attr, 'value') else attr
|
|
2222
|
+
try:
|
|
2223
|
+
if isinstance(obj, ZMap):
|
|
2224
|
+
key = attr_name
|
|
2225
|
+
if isinstance(key, str):
|
|
2226
|
+
key = ZString(key)
|
|
2227
|
+
stack_append(obj.get(key))
|
|
2228
|
+
elif isinstance(obj, dict):
|
|
2229
|
+
stack_append(obj.get(attr_name))
|
|
2230
|
+
elif hasattr(obj, 'get') and hasattr(obj, 'set') and callable(getattr(obj, 'get', None)):
|
|
2231
|
+
# Contract-like objects (e.g., SmartContract) expose state via get/set.
|
|
2232
|
+
stack_append(obj.get(attr_name))
|
|
2233
|
+
else:
|
|
2234
|
+
stack_append(getattr(obj, attr_name, None))
|
|
2235
|
+
except Exception:
|
|
2236
|
+
stack_append(None)
|
|
2237
|
+
elif op_name == "DEFINE_CONTRACT":
|
|
2238
|
+
contract_obj = self._build_smart_contract(operand, stack, stack_pop, const, env)
|
|
2239
|
+
stack_append(contract_obj)
|
|
2240
|
+
|
|
2241
|
+
# --- Blockchain / TX opcodes (sync-safe) ---
|
|
2242
|
+
|
|
2243
|
+
elif op_name == "HASH_BLOCK":
|
|
2244
|
+
block_data = stack_pop() if stack else ""
|
|
2245
|
+
if isinstance(block_data, dict):
|
|
2246
|
+
import json; block_data = json.dumps(block_data, sort_keys=True)
|
|
2247
|
+
if not isinstance(block_data, (bytes, str)): block_data = str(block_data)
|
|
2248
|
+
if isinstance(block_data, str): block_data = block_data.encode('utf-8')
|
|
2249
|
+
try:
|
|
2250
|
+
from Crypto.Hash import keccak as _keccak_mod
|
|
2251
|
+
h = _keccak_mod.new(digest_bits=256, data=block_data)
|
|
2252
|
+
stack_append(h.hexdigest())
|
|
2253
|
+
except ImportError:
|
|
2254
|
+
stack_append(hashlib.sha256(block_data).hexdigest())
|
|
2255
|
+
|
|
2256
|
+
elif op_name == "VERIFY_SIGNATURE":
|
|
2257
|
+
if len(stack) >= 3:
|
|
2258
|
+
pk = stack.pop(); msg = stack.pop(); sig = stack.pop()
|
|
2259
|
+
verify_fn = builtins.get("verify_sig") or env.get("verify_sig")
|
|
2260
|
+
if verify_fn and callable(verify_fn):
|
|
2261
|
+
try:
|
|
2262
|
+
res = verify_fn(sig, msg, pk)
|
|
2263
|
+
except Exception:
|
|
2264
|
+
res = False
|
|
2265
|
+
stack_append(res)
|
|
2266
|
+
else:
|
|
2267
|
+
try:
|
|
2268
|
+
from ..blockchain.crypto import CryptoPlugin
|
|
2269
|
+
sig_s = sig.value if hasattr(sig, 'value') else str(sig)
|
|
2270
|
+
msg_s = msg.value if hasattr(msg, 'value') else str(msg)
|
|
2271
|
+
pk_s = pk.value if hasattr(pk, 'value') else str(pk)
|
|
2272
|
+
stack_append(CryptoPlugin.verify_signature(msg_s, sig_s, pk_s))
|
|
2273
|
+
except ImportError:
|
|
2274
|
+
stack_append(False)
|
|
2275
|
+
else:
|
|
2276
|
+
stack_append(False)
|
|
2277
|
+
|
|
2278
|
+
elif op_name == "MERKLE_ROOT":
|
|
2279
|
+
leaf_count = operand if operand is not None else 0
|
|
2280
|
+
if leaf_count <= 0 or len(stack) < leaf_count:
|
|
2281
|
+
stack_append("")
|
|
2282
|
+
else:
|
|
2283
|
+
leaves = [stack.pop() for _ in range(leaf_count)][::-1]
|
|
2284
|
+
hashes = []
|
|
2285
|
+
for leaf in leaves:
|
|
2286
|
+
if isinstance(leaf, dict):
|
|
2287
|
+
import json; leaf = json.dumps(leaf, sort_keys=True)
|
|
2288
|
+
if not isinstance(leaf, (str, bytes)): leaf = str(leaf)
|
|
2289
|
+
if isinstance(leaf, str): leaf = leaf.encode('utf-8')
|
|
2290
|
+
hashes.append(hashlib.sha256(leaf).hexdigest())
|
|
2291
|
+
while len(hashes) > 1:
|
|
2292
|
+
if len(hashes) % 2 != 0: hashes.append(hashes[-1])
|
|
2293
|
+
new_hashes = []
|
|
2294
|
+
for i in range(0, len(hashes), 2):
|
|
2295
|
+
combined = (hashes[i] + hashes[i+1]).encode('utf-8')
|
|
2296
|
+
new_hashes.append(hashlib.sha256(combined).hexdigest())
|
|
2297
|
+
hashes = new_hashes
|
|
2298
|
+
stack_append(hashes[0] if hashes else "")
|
|
2299
|
+
|
|
2300
|
+
elif op_name == "STATE_READ":
|
|
2301
|
+
if operand is None:
|
|
2302
|
+
key = stack_pop()
|
|
2303
|
+
if hasattr(key, 'value'): key = key.value
|
|
2304
|
+
else:
|
|
2305
|
+
key = const(operand)
|
|
2306
|
+
stack_append(env.setdefault("_blockchain_state", {}).get(key))
|
|
2307
|
+
|
|
2308
|
+
elif op_name == "STATE_WRITE":
|
|
2309
|
+
val = stack_pop()
|
|
2310
|
+
if hasattr(val, 'value'): val = val.value
|
|
2311
|
+
if operand is None:
|
|
2312
|
+
key = stack_pop()
|
|
2313
|
+
if hasattr(key, 'value'): key = key.value
|
|
2314
|
+
else:
|
|
2315
|
+
key = const(operand)
|
|
2316
|
+
if env.get("_in_transaction", False):
|
|
2317
|
+
env.setdefault("_tx_pending_state", {})[key] = val
|
|
2318
|
+
else:
|
|
2319
|
+
env.setdefault("_blockchain_state", {})[key] = val
|
|
2320
|
+
|
|
2321
|
+
elif op_name == "TX_BEGIN":
|
|
2322
|
+
tx_stack = env.setdefault("_tx_stack", [])
|
|
2323
|
+
tx_stack.append({
|
|
2324
|
+
"snapshot": dict(env.get("_blockchain_state", {})),
|
|
2325
|
+
"pending": dict(env.get("_tx_pending_state", {})),
|
|
2326
|
+
})
|
|
2327
|
+
env["_in_transaction"] = True
|
|
2328
|
+
env["_tx_pending_state"] = {}
|
|
2329
|
+
env["_tx_snapshot"] = dict(env.get("_blockchain_state", {}))
|
|
2330
|
+
|
|
2331
|
+
elif op_name == "TX_COMMIT":
|
|
2332
|
+
if env.get("_in_transaction", False):
|
|
2333
|
+
env.setdefault("_blockchain_state", {}).update(
|
|
2334
|
+
env.get("_tx_pending_state", {}))
|
|
2335
|
+
env["_tx_pending_state"] = {}
|
|
2336
|
+
env.pop("_tx_snapshot", None)
|
|
2337
|
+
# Restore outer TX if nested
|
|
2338
|
+
tx_stack = env.get("_tx_stack", [])
|
|
2339
|
+
if tx_stack:
|
|
2340
|
+
tx_stack.pop()
|
|
2341
|
+
env["_in_transaction"] = bool(tx_stack)
|
|
2342
|
+
|
|
2343
|
+
elif op_name == "TX_REVERT":
|
|
2344
|
+
if env.get("_in_transaction", False):
|
|
2345
|
+
env["_blockchain_state"] = dict(env.get("_tx_snapshot", {}))
|
|
2346
|
+
env["_tx_pending_state"] = {}
|
|
2347
|
+
env.pop("_tx_snapshot", None)
|
|
2348
|
+
tx_stack = env.get("_tx_stack", [])
|
|
2349
|
+
if tx_stack:
|
|
2350
|
+
outer = tx_stack.pop()
|
|
2351
|
+
if tx_stack:
|
|
2352
|
+
env["_tx_snapshot"] = outer["snapshot"]
|
|
2353
|
+
env["_tx_pending_state"] = outer["pending"]
|
|
2354
|
+
env["_in_transaction"] = bool(env.get("_tx_stack", []))
|
|
2355
|
+
|
|
2356
|
+
elif op_name == "GAS_CHARGE":
|
|
2357
|
+
amount = operand if operand is not None else 0
|
|
2358
|
+
if _gas is not None:
|
|
2359
|
+
if not _gas.consume("GAS_CHARGE", amount=amount):
|
|
2360
|
+
if env.get("_in_transaction", False):
|
|
2361
|
+
env["_blockchain_state"] = dict(env.get("_tx_snapshot", {}))
|
|
2362
|
+
env["_in_transaction"] = False
|
|
2363
|
+
from .gas_metering import OutOfGasError
|
|
2364
|
+
raise OutOfGasError(_gas.gas_used, _gas.gas_limit, "GAS_CHARGE")
|
|
2365
|
+
# Sync env-based counter for backward compat
|
|
2366
|
+
if "_gas_remaining" in env:
|
|
2367
|
+
env["_gas_remaining"] = max(0, env["_gas_remaining"] - amount)
|
|
2368
|
+
else:
|
|
2369
|
+
# Fallback to env-based tracking when no GasMetering
|
|
2370
|
+
current = env.get("_gas_remaining", float('inf'))
|
|
2371
|
+
if current != float('inf'):
|
|
2372
|
+
new_gas = current - amount
|
|
2373
|
+
if new_gas < 0:
|
|
2374
|
+
if env.get("_in_transaction", False):
|
|
2375
|
+
env["_blockchain_state"] = dict(env.get("_tx_snapshot", {}))
|
|
2376
|
+
env["_in_transaction"] = False
|
|
2377
|
+
raise ZEvaluationError(
|
|
2378
|
+
f"Out of gas: required {amount}, remaining {current}")
|
|
2379
|
+
env["_gas_remaining"] = new_gas
|
|
2380
|
+
|
|
2381
|
+
elif op_name == "REQUIRE":
|
|
2382
|
+
message = stack_pop()
|
|
2383
|
+
if hasattr(message, 'value'): message = message.value
|
|
2384
|
+
condition = stack_pop()
|
|
2385
|
+
cond_val = condition.value if hasattr(condition, 'value') else condition
|
|
2386
|
+
if not cond_val:
|
|
2387
|
+
if env.get("_in_transaction", False):
|
|
2388
|
+
env["_blockchain_state"] = dict(env.get("_tx_snapshot", {}))
|
|
2389
|
+
env["_in_transaction"] = False
|
|
2390
|
+
env["_tx_pending_state"] = {}
|
|
2391
|
+
env.pop("_tx_snapshot", None)
|
|
2392
|
+
raise ZEvaluationError(f"Requirement failed: {message}")
|
|
2393
|
+
|
|
2394
|
+
elif op_name == "LEDGER_APPEND":
|
|
2395
|
+
entry = stack_pop()
|
|
2396
|
+
ledger = env.setdefault("_ledger", [])
|
|
2397
|
+
if len(ledger) < 10000: # Size limit
|
|
2398
|
+
if isinstance(entry, dict) and "timestamp" not in entry:
|
|
2399
|
+
entry["timestamp"] = time.time()
|
|
2400
|
+
ledger.append(entry)
|
|
2401
|
+
|
|
2402
|
+
elif op_name == "SETUP_TRY":
|
|
2403
|
+
handler_ip = int(operand) if operand is not None else ip
|
|
2404
|
+
env.setdefault("_try_stack_sync", []).append(handler_ip)
|
|
2405
|
+
|
|
2406
|
+
elif op_name == "POP_TRY":
|
|
2407
|
+
ts = env.get("_try_stack_sync", [])
|
|
2408
|
+
if ts: ts.pop()
|
|
2409
|
+
|
|
2410
|
+
elif op_name == "THROW":
|
|
2411
|
+
exc = stack_pop()
|
|
2412
|
+
ts = env.get("_try_stack_sync", [])
|
|
2413
|
+
if ts:
|
|
2414
|
+
handler_ip = ts.pop()
|
|
2415
|
+
stack_append(exc)
|
|
2416
|
+
ip = handler_ip
|
|
2417
|
+
else:
|
|
2418
|
+
msg = exc.value if hasattr(exc, 'value') else exc
|
|
2419
|
+
raise ZEvaluationError(str(msg))
|
|
2420
|
+
|
|
2421
|
+
elif op_name == "ENABLE_ERROR_MODE":
|
|
2422
|
+
env["_continue_on_error"] = True
|
|
2423
|
+
|
|
2424
|
+
elif op_name in ("PARALLEL_START", "PARALLEL_END"):
|
|
2425
|
+
pass # Marker ops — no-op in stack VM
|
|
2426
|
+
|
|
2427
|
+
elif op_name == "AUDIT_LOG":
|
|
2428
|
+
ts = time.time()
|
|
2429
|
+
data = stack_pop(); action = stack_pop()
|
|
2430
|
+
if hasattr(action, 'value'): action = action.value
|
|
2431
|
+
if hasattr(data, 'value'): data = data.value
|
|
2432
|
+
env.setdefault("_audit_log", []).append(
|
|
2433
|
+
{"timestamp": ts, "action": action, "data": data})
|
|
2434
|
+
|
|
2435
|
+
elif op_name == "RESTRICT_ACCESS":
|
|
2436
|
+
restriction = stack_pop(); prop = stack_pop(); obj = stack_pop()
|
|
2437
|
+
r_key = f"{obj}.{prop}" if prop else str(obj)
|
|
2438
|
+
env.setdefault("_restrictions", {})[r_key] = restriction
|
|
2439
|
+
# Enforcement via TX.caller
|
|
2440
|
+
caller = None
|
|
2441
|
+
tx_obj = env.get("TX")
|
|
2442
|
+
if tx_obj is not None and hasattr(tx_obj, 'get'):
|
|
2443
|
+
from ..object import String as _ZS
|
|
2444
|
+
cv = tx_obj.get(_ZS("caller"))
|
|
2445
|
+
if cv: caller = cv.value if hasattr(cv, 'value') else str(cv)
|
|
2446
|
+
rv = restriction.value if hasattr(restriction, 'value') else restriction
|
|
2447
|
+
if isinstance(rv, str) and rv == "owner_only":
|
|
2448
|
+
owner = env.get("owner")
|
|
2449
|
+
if owner is not None:
|
|
2450
|
+
ov = owner.value if hasattr(owner, 'value') else str(owner)
|
|
2451
|
+
if caller and caller != ov:
|
|
2452
|
+
raise ZEvaluationError(f"Access denied: '{r_key}' restricted to owner only")
|
|
2453
|
+
elif isinstance(rv, (list, tuple)):
|
|
2454
|
+
allowed = [a.value if hasattr(a, 'value') else str(a) for a in rv]
|
|
2455
|
+
if caller and caller not in allowed:
|
|
2456
|
+
raise ZEvaluationError(f"Access denied: '{r_key}' restricted to allowed addresses")
|
|
2457
|
+
|
|
2458
|
+
else:
|
|
2459
|
+
# Truly unknown op — fallback to async path
|
|
2460
|
+
return self._run_coroutine_sync(self._run_stack_bytecode(bytecode, debug))
|
|
2461
|
+
|
|
2462
|
+
return stack_pop() if stack else None
|
|
2463
|
+
|
|
2464
|
+
def _build_smart_contract(self, operand, stack, stack_pop, const, env):
|
|
2465
|
+
"""Create a real SmartContract from DEFINE_CONTRACT bytecode.
|
|
2466
|
+
|
|
2467
|
+
This mirrors the interpreter's eval_contract_statement logic:
|
|
2468
|
+
1. Pop evaluated storage initial values from the stack
|
|
2469
|
+
2. Create Action objects from the AST action nodes
|
|
2470
|
+
3. Construct a SmartContract with proper storage, deploy lifecycle
|
|
2471
|
+
4. Run the constructor if one exists
|
|
2472
|
+
"""
|
|
2473
|
+
from ..environment import Environment
|
|
2474
|
+
from ..object import Action, Null, Map, String, Integer
|
|
2475
|
+
from ..security import SmartContract
|
|
2476
|
+
|
|
2477
|
+
# Unpack operand: (ast_constant_index, storage_var_count)
|
|
2478
|
+
if isinstance(operand, tuple):
|
|
2479
|
+
ast_idx, storage_count = operand
|
|
2480
|
+
else:
|
|
2481
|
+
# Legacy single-int operand — treat as member_count with no AST
|
|
2482
|
+
ast_idx = None
|
|
2483
|
+
storage_count = operand or 0
|
|
2484
|
+
|
|
2485
|
+
# Pop contract name (pushed last, popped first)
|
|
2486
|
+
contract_name_raw = stack_pop()
|
|
2487
|
+
contract_name = contract_name_raw.value if hasattr(contract_name_raw, 'value') else str(contract_name_raw)
|
|
2488
|
+
|
|
2489
|
+
# Pop storage values (name, value pairs) in reverse push order
|
|
2490
|
+
storage = {}
|
|
2491
|
+
for _ in range(storage_count):
|
|
2492
|
+
raw_val = stack_pop()
|
|
2493
|
+
raw_name = stack_pop()
|
|
2494
|
+
var_name = raw_name.value if hasattr(raw_name, 'value') else str(raw_name)
|
|
2495
|
+
storage[var_name] = self._wrap_for_builtin(raw_val)
|
|
2496
|
+
|
|
2497
|
+
# Retrieve the AST node from the constants pool
|
|
2498
|
+
ast_node = const(ast_idx) if ast_idx is not None else None
|
|
2499
|
+
if ast_node is None:
|
|
2500
|
+
# Can't build a proper contract without the AST — return a Map fallback
|
|
2501
|
+
return ZMap({})
|
|
2502
|
+
|
|
2503
|
+
# Build a bridge Environment so Action closures can resolve outer vars
|
|
2504
|
+
bridge_env = Environment()
|
|
2505
|
+
if isinstance(env, dict):
|
|
2506
|
+
for k, v in env.items():
|
|
2507
|
+
bridge_env.set(k, self._wrap_for_builtin(v))
|
|
2508
|
+
elif hasattr(env, 'items'):
|
|
2509
|
+
for k, v in env.items():
|
|
2510
|
+
bridge_env.set(k, self._wrap_for_builtin(v))
|
|
2511
|
+
|
|
2512
|
+
# Create Action objects from AST action nodes
|
|
2513
|
+
actions = {}
|
|
2514
|
+
for act in getattr(ast_node, 'actions', []):
|
|
2515
|
+
act_name = act.name.value if hasattr(act.name, 'value') else str(act.name)
|
|
2516
|
+
action_obj = Action(act.parameters, act.body, bridge_env)
|
|
2517
|
+
actions[act_name] = action_obj
|
|
2518
|
+
|
|
2519
|
+
# Retrieve storage_vars AST nodes for SmartContract metadata
|
|
2520
|
+
storage_vars = getattr(ast_node, 'storage_vars', [])
|
|
2521
|
+
|
|
2522
|
+
# Create the real SmartContract
|
|
2523
|
+
contract = SmartContract(contract_name, storage_vars, actions)
|
|
2524
|
+
contract.deploy(evaluated_storage_values=storage)
|
|
2525
|
+
|
|
2526
|
+
# Run constructor if present
|
|
2527
|
+
if 'constructor' in actions:
|
|
2528
|
+
constructor = actions['constructor']
|
|
2529
|
+
contract_env = Environment(outer=bridge_env)
|
|
2530
|
+
|
|
2531
|
+
# Set up TX context
|
|
2532
|
+
import time as _time
|
|
2533
|
+
tx_context = Map({
|
|
2534
|
+
String("caller"): String("system"),
|
|
2535
|
+
String("timestamp"): Integer(int(_time.time())),
|
|
2536
|
+
})
|
|
2537
|
+
contract_env.set("TX", tx_context)
|
|
2538
|
+
|
|
2539
|
+
# Pre-populate environment with storage variables
|
|
2540
|
+
for sv in storage_vars:
|
|
2541
|
+
var_name = sv.name.value if hasattr(sv.name, 'value') else str(getattr(sv, 'name', ''))
|
|
2542
|
+
initial_val = contract.storage.get(var_name)
|
|
2543
|
+
if initial_val is not None:
|
|
2544
|
+
contract_env.set(var_name, initial_val)
|
|
2545
|
+
|
|
2546
|
+
# Execute constructor body via the evaluator
|
|
2547
|
+
try:
|
|
2548
|
+
from ..evaluator.core import Evaluator
|
|
2549
|
+
if self._action_evaluator is None:
|
|
2550
|
+
self._action_evaluator = Evaluator(use_vm=False)
|
|
2551
|
+
self._action_evaluator.eval_node(constructor.body, contract_env, [])
|
|
2552
|
+
except Exception as _ctor_err:
|
|
2553
|
+
# Log the error — silent failures can leave security-critical
|
|
2554
|
+
# storage (e.g. owner) uninitialised.
|
|
2555
|
+
import logging as _logging
|
|
2556
|
+
_logging.getLogger("zexus.vm").warning(
|
|
2557
|
+
"Contract '%s' constructor failed: %s", contract_name, _ctor_err)
|
|
2558
|
+
|
|
2559
|
+
# Sync modified variables back to storage
|
|
2560
|
+
for sv in storage_vars:
|
|
2561
|
+
var_name = sv.name.value if hasattr(sv.name, 'value') else str(getattr(sv, 'name', ''))
|
|
2562
|
+
val = contract_env.get(var_name)
|
|
2563
|
+
if val is not None:
|
|
2564
|
+
contract.storage.set(var_name, val)
|
|
2565
|
+
|
|
2566
|
+
return contract
|
|
2567
|
+
|
|
2568
|
+
def _invoke_callable_sync(self, fn, args):
|
|
2569
|
+
"""Synchronous callable invocation for fast dispatch."""
|
|
2570
|
+
if fn is None:
|
|
2571
|
+
return None
|
|
2572
|
+
real_fn = fn.fn if hasattr(fn, "fn") else fn
|
|
2573
|
+
ZAction, ZLambda = _get_action_types()
|
|
2574
|
+
if ZAction is not None and isinstance(real_fn, (ZAction, ZLambda)):
|
|
2575
|
+
# Try to compile to bytecode and execute in VM (fast path)
|
|
2576
|
+
action_bytecode = None
|
|
2577
|
+
try:
|
|
2578
|
+
if hasattr(real_fn, '_cached_bytecode'):
|
|
2579
|
+
action_bytecode = real_fn._cached_bytecode
|
|
2580
|
+
else:
|
|
2581
|
+
from ..evaluator.bytecode_compiler import EvaluatorBytecodeCompiler
|
|
2582
|
+
compiler = EvaluatorBytecodeCompiler(use_cache=False)
|
|
2583
|
+
action_bytecode = compiler.compile(real_fn.body, optimize=True)
|
|
2584
|
+
if action_bytecode and not compiler.errors:
|
|
2585
|
+
real_fn._cached_bytecode = action_bytecode
|
|
2586
|
+
except Exception:
|
|
2587
|
+
action_bytecode = None
|
|
2588
|
+
|
|
2589
|
+
if action_bytecode:
|
|
2590
|
+
# Execute via VM (fast)
|
|
2591
|
+
call_args = [self._wrap_for_builtin(arg) for arg in args]
|
|
2592
|
+
params = real_fn.parameters if hasattr(real_fn, 'parameters') else []
|
|
2593
|
+
local_env = {k.value if hasattr(k, 'value') else k: v for k, v in zip(params, call_args)}
|
|
2594
|
+
inner_vm = VM.create_child(parent_vm=self, env=local_env)
|
|
2595
|
+
try:
|
|
2596
|
+
result = inner_vm._run_stack_bytecode_sync(action_bytecode, debug=False)
|
|
2597
|
+
finally:
|
|
2598
|
+
self._return_vm_to_pool(inner_vm)
|
|
2599
|
+
return self._unwrap_after_builtin(result)
|
|
2600
|
+
else:
|
|
2601
|
+
# Fallback to interpreter (slow)
|
|
2602
|
+
try:
|
|
2603
|
+
from ..evaluator.core import Evaluator
|
|
2604
|
+
if self._action_evaluator is None:
|
|
2605
|
+
self._action_evaluator = Evaluator(use_vm=False)
|
|
2606
|
+
call_args = [self._wrap_for_builtin(arg) for arg in args]
|
|
2607
|
+
result = self._action_evaluator.apply_function(real_fn, call_args)
|
|
2608
|
+
return self._unwrap_after_builtin(result)
|
|
2609
|
+
except Exception:
|
|
2610
|
+
return None
|
|
2611
|
+
if callable(real_fn) and not _iscoroutinefunction(real_fn):
|
|
2612
|
+
try:
|
|
2613
|
+
wrap_args = hasattr(fn, "fn")
|
|
2614
|
+
call_args = [self._wrap_for_builtin(arg) for arg in args] if wrap_args else list(args)
|
|
2615
|
+
result = real_fn(*call_args)
|
|
2616
|
+
return self._unwrap_after_builtin(result) if wrap_args else result
|
|
2617
|
+
except Exception:
|
|
2618
|
+
return None
|
|
2619
|
+
if isinstance(fn, dict):
|
|
2620
|
+
# Function descriptor - execute bytecode
|
|
2621
|
+
bytecode = fn.get("bytecode")
|
|
2622
|
+
if bytecode:
|
|
2623
|
+
params = fn.get("parameters", [])
|
|
2624
|
+
local_env = {}
|
|
2625
|
+
for i, p in enumerate(params):
|
|
2626
|
+
pname = p.get("name") if isinstance(p, dict) else str(p)
|
|
2627
|
+
local_env[pname] = args[i] if i < len(args) else None
|
|
2628
|
+
# Share parent's gas metering so nested calls can't evade limits
|
|
2629
|
+
child_vm = VM.create_child(parent_vm=self, env=local_env)
|
|
2630
|
+
try:
|
|
2631
|
+
return child_vm._run_stack_bytecode_sync(bytecode, debug=False)
|
|
2632
|
+
finally:
|
|
2633
|
+
self._return_vm_to_pool(child_vm)
|
|
2634
|
+
# Fallback for async callables
|
|
2635
|
+
if _iscoroutinefunction(real_fn):
|
|
2636
|
+
return self._run_coroutine_sync(real_fn(*args))
|
|
2637
|
+
return None
|
|
2638
|
+
|
|
2639
|
+
# ==================== Core Execution: Stack Bytecode ====================
|
|
2640
|
+
|
|
2641
|
+
async def _run_stack_bytecode(self, bytecode, debug=False):
|
|
2642
|
+
# 0. Optional bytecode optimizations (peephole, SSA)
|
|
2643
|
+
consts = list(getattr(bytecode, "constants", []))
|
|
2644
|
+
instrs = list(getattr(bytecode, "instructions", []))
|
|
2645
|
+
|
|
2646
|
+
fast_single_shot = (
|
|
2647
|
+
self.fast_single_shot
|
|
2648
|
+
and isinstance(self.single_shot_max_instructions, int)
|
|
2649
|
+
and len(instrs) <= self.single_shot_max_instructions
|
|
2650
|
+
)
|
|
2651
|
+
|
|
2652
|
+
if not fast_single_shot and self.enable_bytecode_optimizer and self.bytecode_optimizer:
|
|
2653
|
+
try:
|
|
2654
|
+
normalized_for_opt: List[Tuple[Any, Any]] = []
|
|
2655
|
+
for instr in instrs:
|
|
2656
|
+
if instr is None:
|
|
2657
|
+
continue
|
|
2658
|
+
if isinstance(instr, tuple) and len(instr) >= 2:
|
|
2659
|
+
op = instr[0]
|
|
2660
|
+
operand = instr[1] if len(instr) == 2 else tuple(instr[1:])
|
|
2661
|
+
op_name = op.name if hasattr(op, "name") else op
|
|
2662
|
+
normalized_for_opt.append((str(op_name), operand))
|
|
2663
|
+
instrs = self.bytecode_optimizer.optimize(normalized_for_opt, consts)
|
|
2664
|
+
except Exception:
|
|
2665
|
+
pass
|
|
2666
|
+
|
|
2667
|
+
# Peephole optimization with constant pool awareness
|
|
2668
|
+
if not fast_single_shot and self.enable_peephole_optimizer and self.peephole_optimizer:
|
|
2669
|
+
try:
|
|
2670
|
+
instrs, consts = self.peephole_optimizer.optimize_bytecode(instrs, consts)
|
|
2671
|
+
except Exception:
|
|
2672
|
+
pass
|
|
2673
|
+
|
|
2674
|
+
# Normalize opcodes to names for SSA pipeline and stack dispatch
|
|
2675
|
+
normalized_instrs: List[Tuple[Any, Any]] = []
|
|
2676
|
+
for instr in instrs:
|
|
2677
|
+
if instr is None:
|
|
2678
|
+
continue
|
|
2679
|
+
if isinstance(instr, tuple) and len(instr) >= 2:
|
|
2680
|
+
op = instr[0]
|
|
2681
|
+
operand = instr[1] if len(instr) == 2 else tuple(instr[1:])
|
|
2682
|
+
op_name = op.name if hasattr(op, "name") else op
|
|
2683
|
+
normalized_instrs.append((op_name, operand))
|
|
2684
|
+
|
|
2685
|
+
instrs = normalized_instrs
|
|
2686
|
+
|
|
2687
|
+
if not self._native_jit_auto_enabled:
|
|
2688
|
+
self._opcode_exec_count += len(instrs)
|
|
2689
|
+
|
|
2690
|
+
if not fast_single_shot and self.enable_ssa and self.ssa_converter:
|
|
2691
|
+
try:
|
|
2692
|
+
ssa_program = self.ssa_converter.convert_to_ssa(instrs)
|
|
2693
|
+
ssa_instrs = destruct_ssa(ssa_program)
|
|
2694
|
+
instrs, consts = self._normalize_ssa_instructions(ssa_instrs, consts)
|
|
2695
|
+
except Exception:
|
|
2696
|
+
pass
|
|
2697
|
+
|
|
2698
|
+
# 1. JIT Check (with thread safety)
|
|
2699
|
+
if self.use_jit and self.jit_compiler:
|
|
2700
|
+
jit_function = None
|
|
2701
|
+
with self._jit_lock:
|
|
2702
|
+
bytecode_hash = self.jit_compiler._hash_bytecode(bytecode)
|
|
2703
|
+
jit_function = self.jit_compiler.compilation_cache.get(bytecode_hash)
|
|
2704
|
+
|
|
2705
|
+
if jit_function:
|
|
2706
|
+
try:
|
|
2707
|
+
start_t = time.perf_counter()
|
|
2708
|
+
stack = []
|
|
2709
|
+
result = jit_function(self, stack, self.env)
|
|
2710
|
+
with self._jit_lock:
|
|
2711
|
+
self.jit_compiler.stats.cache_hits += 1
|
|
2712
|
+
self.jit_compiler.record_execution_time(bytecode_hash, time.perf_counter() - start_t, ExecutionTier.JIT_NATIVE)
|
|
2713
|
+
if debug: print(f"[VM JIT] Executed cached function")
|
|
2714
|
+
return result
|
|
2715
|
+
except Exception as e:
|
|
2716
|
+
if debug: print(f"[VM JIT] Failed: {e}, falling back")
|
|
2717
|
+
|
|
2718
|
+
# 2. Bytecode Execution Setup
|
|
2719
|
+
ip = 0
|
|
2720
|
+
trace_interval = 0
|
|
2721
|
+
try:
|
|
2722
|
+
trace_interval = int(os.environ.get("ZEXUS_VM_TRACE_INTERVAL", "0"))
|
|
2723
|
+
except Exception:
|
|
2724
|
+
trace_interval = 0
|
|
2725
|
+
trace_counter = 0
|
|
2726
|
+
running = True
|
|
2727
|
+
return_value = None
|
|
2728
|
+
profile_flag = os.environ.get("ZEXUS_VM_PROFILE_OPS")
|
|
2729
|
+
profile_ops = profile_flag is not None and profile_flag.lower() not in ("0", "false", "off")
|
|
2730
|
+
profile_verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
|
|
2731
|
+
profile_verbose = profile_verbose_flag and profile_verbose_flag.lower() not in ("0", "false", "off")
|
|
2732
|
+
opcode_counts: Optional[Dict[str, int]] = {} if profile_ops else None
|
|
2733
|
+
if profile_ops and profile_verbose:
|
|
2734
|
+
print(f"[VM DEBUG] opcode profiling enabled; instrs={len(instrs)}")
|
|
2735
|
+
gas_metering = self.gas_metering
|
|
2736
|
+
gas_light = self.enable_gas_light and gas_metering is not None
|
|
2737
|
+
gas_consume = gas_metering.consume if gas_metering else None
|
|
2738
|
+
gas_consume_light = gas_metering.consume_light if gas_metering else None
|
|
2739
|
+
gas_enabled = self.enable_gas_metering and gas_metering is not None
|
|
2740
|
+
trace_ip_range = None
|
|
2741
|
+
trace_ip_env = os.environ.get("ZEXUS_VM_TRACE_IP_RANGE")
|
|
2742
|
+
if trace_ip_env:
|
|
2743
|
+
try:
|
|
2744
|
+
parts = str(trace_ip_env).split("-", 1)
|
|
2745
|
+
if len(parts) == 2:
|
|
2746
|
+
trace_ip_range = (int(parts[0]), int(parts[1]))
|
|
2747
|
+
except Exception:
|
|
2748
|
+
trace_ip_range = None
|
|
2749
|
+
|
|
2750
|
+
trace_loads_flag = os.environ.get("ZEXUS_VM_TRACE_LOADS")
|
|
2751
|
+
trace_loads_active = trace_loads_flag and trace_loads_flag.lower() not in ("0", "false", "off")
|
|
2752
|
+
trace_calls_flag = os.environ.get("ZEXUS_VM_TRACE_CALLS")
|
|
2753
|
+
trace_calls_active = trace_calls_flag and trace_calls_flag.lower() not in ("0", "false", "off")
|
|
2754
|
+
trace_targets_flag = os.environ.get("ZEXUS_VM_TRACE_METHOD_TARGETS")
|
|
2755
|
+
trace_targets_active = trace_targets_flag and trace_targets_flag.lower() not in ("0", "false", "off")
|
|
2756
|
+
# Hoist CALL_METHOD env lookups (were computed per-call)
|
|
2757
|
+
_trace_stack_flag = os.environ.get("ZEXUS_VM_TRACE_STACK")
|
|
2758
|
+
_trace_stack_active = bool(_trace_stack_flag and _trace_stack_flag.lower() not in ("0", "false", "off"))
|
|
2759
|
+
_trace_method_ops_flag = os.environ.get("ZEXUS_VM_TRACE_METHOD_OPS")
|
|
2760
|
+
_trace_method_ops_targets = None
|
|
2761
|
+
if _trace_method_ops_flag:
|
|
2762
|
+
try:
|
|
2763
|
+
_trace_method_ops_targets = [m.strip() for m in _trace_method_ops_flag.split(",") if m.strip()]
|
|
2764
|
+
except Exception:
|
|
2765
|
+
_trace_method_ops_targets = None
|
|
2766
|
+
_verbose_active = profile_verbose
|
|
2767
|
+
# Cached local ref to iscoroutinefunction (avoids asyncio.X attribute lookup)
|
|
2768
|
+
_iscoroutinefunction_local = _iscoroutinefunction
|
|
2769
|
+
|
|
2770
|
+
# Pre-resolve security module for CALL_METHOD
|
|
2771
|
+
_cached_security = _get_security_mod()
|
|
2772
|
+
# Pre-resolve Action/Lambda types for _invoke_callable_sync
|
|
2773
|
+
_cached_ZAction, _cached_ZLambda = _get_action_types()
|
|
2774
|
+
|
|
2775
|
+
class _EvalStack:
|
|
2776
|
+
__slots__ = ("data", "sp")
|
|
2777
|
+
|
|
2778
|
+
def __init__(self, capacity: int):
|
|
2779
|
+
base = max(32, capacity)
|
|
2780
|
+
self.data = [None] * base
|
|
2781
|
+
self.sp = 0
|
|
2782
|
+
|
|
2783
|
+
def _ensure_capacity(self):
|
|
2784
|
+
if self.sp >= len(self.data):
|
|
2785
|
+
self.data.extend([None] * len(self.data))
|
|
2786
|
+
|
|
2787
|
+
def append(self, value: Any):
|
|
2788
|
+
self._ensure_capacity()
|
|
2789
|
+
self.data[self.sp] = value
|
|
2790
|
+
self.sp += 1
|
|
2791
|
+
|
|
2792
|
+
def pop(self):
|
|
2793
|
+
if self.sp == 0:
|
|
2794
|
+
raise IndexError("pop from empty stack")
|
|
2795
|
+
self.sp -= 1
|
|
2796
|
+
value = self.data[self.sp]
|
|
2797
|
+
self.data[self.sp] = None
|
|
2798
|
+
return value
|
|
955
2799
|
|
|
956
2800
|
def peek(self, default: Any = None):
|
|
957
2801
|
if self.sp == 0:
|
|
@@ -975,69 +2819,128 @@ class VM:
|
|
|
975
2819
|
return self.data[:self.sp]
|
|
976
2820
|
|
|
977
2821
|
stack = _EvalStack(len(instrs) * 2 if instrs else 32)
|
|
2822
|
+
stack_append = stack.append
|
|
2823
|
+
stack_pop = stack.pop
|
|
2824
|
+
call_cache: Dict[str, Tuple[Any, int]] = {}
|
|
978
2825
|
|
|
979
2826
|
def const(idx):
|
|
980
2827
|
if isinstance(idx, int):
|
|
981
2828
|
return consts[idx] if 0 <= idx < len(consts) else None
|
|
982
2829
|
return idx
|
|
983
2830
|
|
|
2831
|
+
missing = object()
|
|
2832
|
+
env_get = self.env.get
|
|
2833
|
+
closure_get = self._closure_cells.get
|
|
2834
|
+
builtins_get = self.builtins.get
|
|
2835
|
+
name_cache = self._name_cache
|
|
2836
|
+
|
|
984
2837
|
# Lexical Resolution Helper (Closures/Cells)
|
|
985
2838
|
def _resolve(name):
|
|
2839
|
+
cached = name_cache.get(name)
|
|
2840
|
+
if cached and cached[1] == self._env_version:
|
|
2841
|
+
return cached[0]
|
|
986
2842
|
# 1. Local
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
2843
|
+
val = env_get(name, missing)
|
|
2844
|
+
if val is not missing:
|
|
2845
|
+
resolved = val.value if isinstance(val, Cell) else val
|
|
2846
|
+
name_cache[name] = (resolved, self._env_version)
|
|
2847
|
+
return resolved
|
|
990
2848
|
# 2. Closure Cells (attached to VM)
|
|
991
|
-
|
|
992
|
-
|
|
2849
|
+
cell = closure_get(name)
|
|
2850
|
+
if cell is not None:
|
|
2851
|
+
resolved = cell.value
|
|
2852
|
+
name_cache[name] = (resolved, self._env_version)
|
|
2853
|
+
return resolved
|
|
993
2854
|
# 3. Parent Chain
|
|
994
2855
|
p = self._parent_env
|
|
995
2856
|
while p is not None:
|
|
996
2857
|
if isinstance(p, VM):
|
|
997
|
-
|
|
998
|
-
|
|
2858
|
+
p_val = p.env.get(name, missing)
|
|
2859
|
+
if p_val is not missing:
|
|
2860
|
+
val = p_val
|
|
999
2861
|
return val.value if isinstance(val, Cell) else val
|
|
1000
|
-
|
|
1001
|
-
|
|
2862
|
+
p_cell = p._closure_cells.get(name)
|
|
2863
|
+
if p_cell is not None:
|
|
2864
|
+
return p_cell.value
|
|
1002
2865
|
p = p._parent_env
|
|
1003
2866
|
else:
|
|
1004
|
-
if name in p:
|
|
2867
|
+
if name in p:
|
|
2868
|
+
return p[name]
|
|
1005
2869
|
p = None
|
|
1006
2870
|
return None
|
|
1007
2871
|
|
|
1008
2872
|
def _store(name, value):
|
|
1009
2873
|
# Update existing Cell in local env
|
|
1010
2874
|
if name in self.env and isinstance(self.env[name], Cell):
|
|
1011
|
-
self.env[name].value = value
|
|
2875
|
+
self.env[name].value = value
|
|
2876
|
+
self._bump_env_version(name, value)
|
|
2877
|
+
return
|
|
1012
2878
|
# Update local non-cell
|
|
1013
2879
|
if name in self.env:
|
|
1014
|
-
self.env[name] = value
|
|
2880
|
+
self.env[name] = value
|
|
2881
|
+
self._bump_env_version(name, value)
|
|
2882
|
+
return
|
|
1015
2883
|
# Update Closure Cell
|
|
1016
2884
|
if name in self._closure_cells:
|
|
1017
|
-
self._closure_cells[name].value = value
|
|
2885
|
+
self._closure_cells[name].value = value
|
|
2886
|
+
self._bump_env_version(name, value)
|
|
2887
|
+
return
|
|
1018
2888
|
# Update Parent Chain
|
|
1019
2889
|
p = self._parent_env
|
|
1020
2890
|
while p is not None:
|
|
1021
2891
|
if isinstance(p, VM):
|
|
1022
2892
|
if name in p._closure_cells:
|
|
1023
|
-
p._closure_cells[name].value = value
|
|
2893
|
+
p._closure_cells[name].value = value
|
|
2894
|
+
p._bump_env_version(name, value)
|
|
2895
|
+
self._bump_env_version(name, value)
|
|
2896
|
+
return
|
|
1024
2897
|
if name in p.env:
|
|
1025
|
-
p.env[name] = value
|
|
2898
|
+
p.env[name] = value
|
|
2899
|
+
p._bump_env_version(name, value)
|
|
2900
|
+
self._bump_env_version(name, value)
|
|
2901
|
+
return
|
|
1026
2902
|
p = p._parent_env
|
|
1027
2903
|
else:
|
|
1028
2904
|
if name in p:
|
|
1029
|
-
p[name] = value
|
|
2905
|
+
p[name] = value
|
|
2906
|
+
self._bump_env_version(name, value)
|
|
2907
|
+
return
|
|
1030
2908
|
p = None
|
|
1031
2909
|
# Default: Create local
|
|
1032
2910
|
self.env[name] = value
|
|
2911
|
+
self._bump_env_version(name, value)
|
|
2912
|
+
|
|
2913
|
+
def _resolve_callable(name):
|
|
2914
|
+
cached = call_cache.get(name)
|
|
2915
|
+
if cached and cached[1] == self._env_version:
|
|
2916
|
+
return cached[0]
|
|
2917
|
+
fn = None
|
|
2918
|
+
try:
|
|
2919
|
+
fn = builtins_get(name)
|
|
2920
|
+
except Exception:
|
|
2921
|
+
fn = None
|
|
2922
|
+
if fn is None:
|
|
2923
|
+
fn = _resolve(name)
|
|
2924
|
+
call_cache[name] = (fn, self._env_version)
|
|
2925
|
+
return fn
|
|
1033
2926
|
|
|
1034
2927
|
def _unwrap(value):
|
|
2928
|
+
if isinstance(value, ZNull):
|
|
2929
|
+
return None
|
|
1035
2930
|
return value.value if hasattr(value, 'value') else value
|
|
1036
2931
|
|
|
1037
2932
|
def _binary_op(func):
|
|
1038
2933
|
def wrapper(_):
|
|
1039
2934
|
b = _unwrap(stack.pop() if stack else 0)
|
|
1040
2935
|
a = _unwrap(stack.pop() if stack else 0)
|
|
2936
|
+
if a is None: a = 0
|
|
2937
|
+
if b is None: b = 0
|
|
2938
|
+
if isinstance(a, ZEvaluationError):
|
|
2939
|
+
stack.append(a)
|
|
2940
|
+
return
|
|
2941
|
+
if isinstance(b, ZEvaluationError):
|
|
2942
|
+
stack.append(b)
|
|
2943
|
+
return
|
|
1041
2944
|
try:
|
|
1042
2945
|
stack.append(func(a, b))
|
|
1043
2946
|
except Exception as exc:
|
|
@@ -1048,30 +2951,57 @@ class VM:
|
|
|
1048
2951
|
|
|
1049
2952
|
def _binary_bool_op(func):
|
|
1050
2953
|
def wrapper(_):
|
|
1051
|
-
b = stack.pop() if stack else None
|
|
1052
|
-
a = stack.pop() if stack else None
|
|
2954
|
+
b = _unwrap(stack.pop() if stack else None)
|
|
2955
|
+
a = _unwrap(stack.pop() if stack else None)
|
|
2956
|
+
if isinstance(a, ZEvaluationError):
|
|
2957
|
+
stack.append(a)
|
|
2958
|
+
return
|
|
2959
|
+
if isinstance(b, ZEvaluationError):
|
|
2960
|
+
stack.append(b)
|
|
2961
|
+
return
|
|
1053
2962
|
stack.append(func(a, b))
|
|
1054
2963
|
return wrapper
|
|
1055
2964
|
|
|
1056
2965
|
async def _op_call_name(operand):
|
|
1057
2966
|
if not operand:
|
|
1058
|
-
|
|
2967
|
+
stack_append(None)
|
|
1059
2968
|
return
|
|
1060
2969
|
name_idx, arg_count = operand
|
|
1061
2970
|
func_name = const(name_idx)
|
|
1062
|
-
|
|
1063
|
-
|
|
2971
|
+
if arg_count:
|
|
2972
|
+
args = [stack_pop() if stack else None for _ in range(arg_count)]
|
|
2973
|
+
args.reverse()
|
|
2974
|
+
else:
|
|
2975
|
+
args = []
|
|
2976
|
+
fn = None
|
|
2977
|
+
try:
|
|
2978
|
+
fn = builtins_get(func_name)
|
|
2979
|
+
except Exception:
|
|
2980
|
+
fn = None
|
|
2981
|
+
if fn is None:
|
|
2982
|
+
fn = _resolve_callable(func_name)
|
|
1064
2983
|
if fn is None:
|
|
1065
2984
|
fallback_res = self._call_fallback_builtin(func_name, args)
|
|
1066
|
-
|
|
2985
|
+
stack_append(fallback_res)
|
|
2986
|
+
return
|
|
2987
|
+
real_fn = fn.fn if hasattr(fn, "fn") else fn
|
|
2988
|
+
if callable(real_fn) and not _iscoroutinefunction_local(real_fn):
|
|
2989
|
+
res = self._invoke_callable_sync(fn, args)
|
|
2990
|
+
stack_append(res)
|
|
1067
2991
|
return
|
|
1068
2992
|
res = await self._invoke_callable_or_funcdesc(fn, args)
|
|
1069
|
-
|
|
2993
|
+
stack_append(res)
|
|
1070
2994
|
|
|
1071
2995
|
async def _op_call_top(arg_count):
|
|
1072
2996
|
count = arg_count or 0
|
|
1073
|
-
|
|
1074
|
-
|
|
2997
|
+
# Use stack_pop to avoid crash on empty stack
|
|
2998
|
+
args = [stack_pop() for _ in range(count)][::-1] if count else []
|
|
2999
|
+
fn_obj = stack_pop()
|
|
3000
|
+
real_fn = fn_obj.fn if hasattr(fn_obj, "fn") else fn_obj
|
|
3001
|
+
if callable(real_fn) and not _iscoroutinefunction_local(real_fn):
|
|
3002
|
+
res = self._invoke_callable_sync(fn_obj, args)
|
|
3003
|
+
stack.append(res)
|
|
3004
|
+
return
|
|
1075
3005
|
res = await self._invoke_callable_or_funcdesc(fn_obj, args)
|
|
1076
3006
|
stack.append(res)
|
|
1077
3007
|
|
|
@@ -1081,21 +3011,157 @@ class VM:
|
|
|
1081
3011
|
return
|
|
1082
3012
|
|
|
1083
3013
|
method_idx, arg_count = operand
|
|
1084
|
-
|
|
1085
|
-
|
|
3014
|
+
if _trace_stack_active:
|
|
3015
|
+
if len(stack) < arg_count + 1:
|
|
3016
|
+
try:
|
|
3017
|
+
window = []
|
|
3018
|
+
start = max(0, ip - 12)
|
|
3019
|
+
for k in range(start, min(len(instrs), ip + 1)):
|
|
3020
|
+
instr = instrs[k]
|
|
3021
|
+
if instr is None:
|
|
3022
|
+
continue
|
|
3023
|
+
opk = instr[0] if isinstance(instr, tuple) else instr
|
|
3024
|
+
namek = opk.name if hasattr(opk, "name") else str(opk)
|
|
3025
|
+
operk = instr[1] if isinstance(instr, tuple) and len(instr) > 1 else None
|
|
3026
|
+
if namek in ("LOAD_NAME", "LOAD_CONST"):
|
|
3027
|
+
try:
|
|
3028
|
+
val = const(operk)
|
|
3029
|
+
except Exception:
|
|
3030
|
+
val = operk
|
|
3031
|
+
window.append(f"{k}:{namek}={val}")
|
|
3032
|
+
else:
|
|
3033
|
+
window.append(f"{k}:{namek}")
|
|
3034
|
+
try:
|
|
3035
|
+
tail = stack.snapshot()[-8:]
|
|
3036
|
+
except Exception:
|
|
3037
|
+
tail = "<unavailable>"
|
|
3038
|
+
print(
|
|
3039
|
+
f"[VM TRACE] stack_underflow ip={ip} method={const(method_idx)} "
|
|
3040
|
+
f"argc={arg_count} stack={len(stack)} tail={tail} ops={'|'.join(window)}"
|
|
3041
|
+
)
|
|
3042
|
+
except Exception:
|
|
3043
|
+
print(f"[VM TRACE] stack_underflow ip={ip} method={const(method_idx)} argc={arg_count} stack={len(stack)}")
|
|
3044
|
+
if len(stack) < arg_count + 1:
|
|
3045
|
+
missing = (arg_count + 1) - len(stack)
|
|
3046
|
+
for _ in range(missing):
|
|
3047
|
+
stack_append(None)
|
|
3048
|
+
args = [stack_pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
3049
|
+
target = stack_pop() if stack else None
|
|
1086
3050
|
method_name = const(method_idx)
|
|
3051
|
+
if _trace_method_ops_targets:
|
|
3052
|
+
if method_name in _trace_method_ops_targets:
|
|
3053
|
+
try:
|
|
3054
|
+
window = []
|
|
3055
|
+
start = max(0, ip - 10)
|
|
3056
|
+
for k in range(start, min(len(instrs), ip + 2)):
|
|
3057
|
+
instr = instrs[k]
|
|
3058
|
+
if instr is None:
|
|
3059
|
+
continue
|
|
3060
|
+
opk = instr[0] if isinstance(instr, tuple) else instr
|
|
3061
|
+
namek = opk.name if hasattr(opk, "name") else str(opk)
|
|
3062
|
+
operk = instr[1] if isinstance(instr, tuple) and len(instr) > 1 else None
|
|
3063
|
+
if namek in ("LOAD_NAME", "LOAD_CONST", "STORE_NAME"):
|
|
3064
|
+
try:
|
|
3065
|
+
val = const(operk)
|
|
3066
|
+
except Exception:
|
|
3067
|
+
val = operk
|
|
3068
|
+
window.append(f"{k}:{namek}={val}")
|
|
3069
|
+
else:
|
|
3070
|
+
window.append(f"{k}:{namek}")
|
|
3071
|
+
print(f"[VM TRACE] method_ops {method_name} ip={ip} ops={'|'.join(window)}")
|
|
3072
|
+
except Exception:
|
|
3073
|
+
print(f"[VM TRACE] method_ops {method_name} ip={ip}")
|
|
3074
|
+
|
|
3075
|
+
if trace_calls_active:
|
|
3076
|
+
try:
|
|
3077
|
+
interval = int(trace_calls_flag) if trace_calls_flag.isdigit() else 1000
|
|
3078
|
+
except Exception:
|
|
3079
|
+
interval = 1000
|
|
3080
|
+
self._call_method_total += 1
|
|
3081
|
+
if interval > 0 and self._call_method_total % interval == 0:
|
|
3082
|
+
target_type = type(target).__name__ if target is not None else "None"
|
|
3083
|
+
print(
|
|
3084
|
+
f"[VM TRACE] CALL_METHOD total={self._call_method_total} method={method_name} "
|
|
3085
|
+
f"argc={arg_count} target={target_type}"
|
|
3086
|
+
)
|
|
1087
3087
|
|
|
1088
3088
|
if target is None:
|
|
1089
|
-
|
|
3089
|
+
if trace_targets_active:
|
|
3090
|
+
if method_name in ("submit_transaction_fast", "produce_single_tx_block", "produce_blocks_fast_until_empty"):
|
|
3091
|
+
if self._method_target_trace_count < 10:
|
|
3092
|
+
env_val = self.env.get("blockchain") if isinstance(self.env, dict) else None
|
|
3093
|
+
env_type = type(env_val).__name__ if env_val is not None else "None"
|
|
3094
|
+
stack_size = len(stack) if hasattr(stack, "__len__") else -1
|
|
3095
|
+
print(f"[VM TRACE] {method_name} target None; env.blockchain={env_type} arg_count={arg_count} stack={stack_size}")
|
|
3096
|
+
self._method_target_trace_count += 1
|
|
3097
|
+
stack_append(None)
|
|
1090
3098
|
return
|
|
1091
3099
|
|
|
1092
3100
|
result = None
|
|
3101
|
+
if _verbose_active and self._call_method_trace_count < 25:
|
|
3102
|
+
target_type = type(target).__name__
|
|
3103
|
+
preview = []
|
|
3104
|
+
for item in args[:3]:
|
|
3105
|
+
try:
|
|
3106
|
+
preview.append(repr(item))
|
|
3107
|
+
except Exception:
|
|
3108
|
+
preview.append(f"<{type(item).__name__}>")
|
|
3109
|
+
print(f"[VM TRACE] CALL_METHOD {method_name} target={target_type} args={len(args)} preview={preview}")
|
|
3110
|
+
self._call_method_trace_count += 1
|
|
1093
3111
|
try:
|
|
1094
|
-
if
|
|
3112
|
+
if method_name == "set":
|
|
3113
|
+
if isinstance(target, ZMap):
|
|
3114
|
+
if len(args) >= 2:
|
|
3115
|
+
key = args[0]
|
|
3116
|
+
if isinstance(key, ZString):
|
|
3117
|
+
norm_key = key.value
|
|
3118
|
+
elif isinstance(key, str):
|
|
3119
|
+
norm_key = key
|
|
3120
|
+
elif hasattr(key, "inspect"):
|
|
3121
|
+
norm_key = key.inspect()
|
|
3122
|
+
else:
|
|
3123
|
+
norm_key = str(key)
|
|
3124
|
+
existing = target.pairs.get(norm_key)
|
|
3125
|
+
if existing is not None and existing.__class__.__name__ == 'SealedObject':
|
|
3126
|
+
raise ZEvaluationError(f"Cannot modify sealed map key: {key}")
|
|
3127
|
+
target.pairs[norm_key] = args[1]
|
|
3128
|
+
result = args[1]
|
|
3129
|
+
else:
|
|
3130
|
+
result = None
|
|
3131
|
+
elif isinstance(target, ZList):
|
|
3132
|
+
if len(args) >= 2:
|
|
3133
|
+
target.set(args[0], args[1])
|
|
3134
|
+
result = args[1]
|
|
3135
|
+
else:
|
|
3136
|
+
result = None
|
|
3137
|
+
elif isinstance(target, (dict, list)):
|
|
3138
|
+
if len(args) >= 2:
|
|
3139
|
+
target[args[0]] = args[1]
|
|
3140
|
+
result = args[1]
|
|
3141
|
+
else:
|
|
3142
|
+
result = None
|
|
3143
|
+
elif method_name == "get":
|
|
3144
|
+
if isinstance(target, ZMap) and args:
|
|
3145
|
+
result = target.get(args[0])
|
|
3146
|
+
elif isinstance(target, dict) and args:
|
|
3147
|
+
result = target.get(args[0])
|
|
3148
|
+
elif hasattr(target, "call_method"):
|
|
1095
3149
|
wrapped_args = [self._wrap_for_builtin(arg) for arg in args]
|
|
1096
|
-
|
|
3150
|
+
if _cached_security is not None:
|
|
3151
|
+
try:
|
|
3152
|
+
_cached_security._set_vm_action_context(True)
|
|
3153
|
+
except Exception:
|
|
3154
|
+
pass
|
|
3155
|
+
try:
|
|
3156
|
+
result = target.call_method(method_name, wrapped_args)
|
|
3157
|
+
finally:
|
|
3158
|
+
if _cached_security is not None:
|
|
3159
|
+
try:
|
|
3160
|
+
_cached_security._set_vm_action_context(False)
|
|
3161
|
+
except Exception:
|
|
3162
|
+
pass
|
|
1097
3163
|
else:
|
|
1098
|
-
attr =
|
|
3164
|
+
attr = self._get_cached_method(target, method_name)
|
|
1099
3165
|
if callable(attr):
|
|
1100
3166
|
result = attr(*args)
|
|
1101
3167
|
elif isinstance(target, dict) and method_name in target:
|
|
@@ -1103,39 +3169,93 @@ class VM:
|
|
|
1103
3169
|
result = candidate(*args) if callable(candidate) else candidate
|
|
1104
3170
|
else:
|
|
1105
3171
|
result = attr
|
|
3172
|
+
if _verbose_active and self._call_method_trace_count <= 25:
|
|
3173
|
+
print(f"[VM TRACE] CALL_METHOD {method_name} result={result}")
|
|
3174
|
+
# Only check for coroutine/future on paths that can produce them
|
|
3175
|
+
# set/get paths are always sync; call_method/getattr may return coroutines
|
|
3176
|
+
if result is not None and (asyncio.iscoroutine(result) or isinstance(result, asyncio.Future)):
|
|
3177
|
+
if self.async_optimizer:
|
|
3178
|
+
result = await self.async_optimizer.await_optimized(result)
|
|
3179
|
+
else:
|
|
3180
|
+
result = await result
|
|
1106
3181
|
except Exception as exc:
|
|
1107
3182
|
if debug:
|
|
1108
3183
|
print(f"[VM] CALL_METHOD failed for {method_name}: {exc}")
|
|
1109
3184
|
raise
|
|
1110
3185
|
|
|
1111
|
-
|
|
3186
|
+
stack_append(self._unwrap_after_builtin(result))
|
|
3187
|
+
|
|
3188
|
+
stack_append = stack.append
|
|
3189
|
+
stack_pop = stack.pop
|
|
1112
3190
|
|
|
1113
3191
|
def _op_load_const(idx):
|
|
1114
|
-
|
|
3192
|
+
value = const(idx)
|
|
3193
|
+
if self.integer_pool and isinstance(value, int):
|
|
3194
|
+
value = self.integer_pool.get(value)
|
|
3195
|
+
elif self.string_pool and isinstance(value, str):
|
|
3196
|
+
value = self.string_pool.get(value)
|
|
3197
|
+
stack_append(value)
|
|
3198
|
+
if trace_loads_active:
|
|
3199
|
+
if value is None:
|
|
3200
|
+
try:
|
|
3201
|
+
print(f"[VM TRACE] LOAD_CONST None ip={ip - 1} stack={len(stack)}")
|
|
3202
|
+
except Exception:
|
|
3203
|
+
print(f"[VM TRACE] LOAD_CONST None ip={ip - 1}")
|
|
1115
3204
|
|
|
1116
3205
|
def _op_load_name(idx):
|
|
1117
3206
|
name = const(idx)
|
|
1118
|
-
|
|
3207
|
+
stack_append(_resolve(name))
|
|
3208
|
+
if trace_loads_active:
|
|
3209
|
+
if name in ("blockchain", "sender"):
|
|
3210
|
+
try:
|
|
3211
|
+
print(f"[VM TRACE] LOAD_NAME {name} ip={ip - 1} stack={len(stack)}")
|
|
3212
|
+
except Exception:
|
|
3213
|
+
print(f"[VM TRACE] LOAD_NAME {name} ip={ip - 1}")
|
|
1119
3214
|
|
|
1120
3215
|
def _op_store_name(idx):
|
|
1121
3216
|
name = const(idx)
|
|
1122
|
-
val =
|
|
1123
|
-
|
|
3217
|
+
val = stack_pop() if stack else None
|
|
3218
|
+
existing = env_get(name, missing)
|
|
3219
|
+
if existing is not missing and not isinstance(existing, Cell):
|
|
3220
|
+
self.env[name] = val
|
|
3221
|
+
self._bump_env_version(name, val)
|
|
3222
|
+
else:
|
|
3223
|
+
cell = closure_get(name)
|
|
3224
|
+
if cell is not None:
|
|
3225
|
+
cell.value = val
|
|
3226
|
+
self._bump_env_version(name, val)
|
|
3227
|
+
else:
|
|
3228
|
+
_store(name, val)
|
|
1124
3229
|
if self.use_memory_manager and val is not None:
|
|
1125
|
-
self._allocate_managed(val, name=name)
|
|
1126
|
-
|
|
3230
|
+
self._allocate_managed(val, name=name, root=True)
|
|
3231
|
+
return
|
|
1127
3232
|
def _op_pop(_):
|
|
1128
3233
|
if stack:
|
|
1129
|
-
|
|
3234
|
+
stack_pop()
|
|
1130
3235
|
|
|
1131
3236
|
def _op_dup(_):
|
|
1132
3237
|
if stack:
|
|
1133
|
-
stack.
|
|
3238
|
+
stack_append(stack.peek())
|
|
1134
3239
|
|
|
1135
3240
|
def _op_neg(_):
|
|
1136
3241
|
a = _unwrap(stack.pop() if stack else 0)
|
|
1137
3242
|
stack.append(-a)
|
|
1138
3243
|
|
|
3244
|
+
def _op_add(_):
|
|
3245
|
+
b = _unwrap(stack_pop() if stack else 0)
|
|
3246
|
+
a = _unwrap(stack_pop() if stack else 0)
|
|
3247
|
+
if a is None:
|
|
3248
|
+
a = 0
|
|
3249
|
+
if b is None:
|
|
3250
|
+
b = 0
|
|
3251
|
+
if isinstance(a, ZEvaluationError):
|
|
3252
|
+
stack_append(a)
|
|
3253
|
+
return
|
|
3254
|
+
if isinstance(b, ZEvaluationError):
|
|
3255
|
+
stack_append(b)
|
|
3256
|
+
return
|
|
3257
|
+
stack_append(a + b)
|
|
3258
|
+
|
|
1139
3259
|
def _op_not(_):
|
|
1140
3260
|
a = stack.pop() if stack else False
|
|
1141
3261
|
stack.append(not a)
|
|
@@ -1147,7 +3267,8 @@ class VM:
|
|
|
1147
3267
|
def _op_jump_if_false(target):
|
|
1148
3268
|
nonlocal ip
|
|
1149
3269
|
cond = stack.pop() if stack else None
|
|
1150
|
-
|
|
3270
|
+
cond_val = _unwrap(cond)
|
|
3271
|
+
if not cond_val:
|
|
1151
3272
|
ip = target
|
|
1152
3273
|
|
|
1153
3274
|
def _op_return(_):
|
|
@@ -1157,29 +3278,84 @@ class VM:
|
|
|
1157
3278
|
|
|
1158
3279
|
def _op_build_list(count):
|
|
1159
3280
|
total = count if count is not None else 0
|
|
1160
|
-
|
|
1161
|
-
|
|
3281
|
+
if self.list_pool:
|
|
3282
|
+
lst = self.allocate_list(total)
|
|
3283
|
+
if total > 0:
|
|
3284
|
+
for i in range(total - 1, -1, -1):
|
|
3285
|
+
lst[i] = stack.pop() if stack else None
|
|
3286
|
+
stack.append(lst)
|
|
3287
|
+
else:
|
|
3288
|
+
elements = [None] * total
|
|
3289
|
+
for i in range(total - 1, -1, -1):
|
|
3290
|
+
elements[i] = stack.pop() if stack else None
|
|
3291
|
+
stack.append(elements)
|
|
1162
3292
|
|
|
1163
3293
|
def _op_build_map(count):
|
|
1164
3294
|
total = count if count is not None else 0
|
|
1165
3295
|
result = {}
|
|
1166
3296
|
for _ in range(total):
|
|
1167
|
-
val =
|
|
3297
|
+
val = stack_pop() if stack else None
|
|
3298
|
+
key = stack_pop() if stack else None
|
|
1168
3299
|
result[key] = val
|
|
1169
|
-
|
|
3300
|
+
stack_append(result)
|
|
1170
3301
|
|
|
1171
3302
|
def _op_index(_):
|
|
1172
|
-
idx = stack.pop()
|
|
3303
|
+
idx = stack.pop() if stack else None
|
|
3304
|
+
obj = stack.pop() if stack else None
|
|
1173
3305
|
try:
|
|
1174
|
-
|
|
3306
|
+
if isinstance(obj, ZList):
|
|
3307
|
+
stack.append(obj.get(idx))
|
|
3308
|
+
elif isinstance(obj, ZMap):
|
|
3309
|
+
key = idx
|
|
3310
|
+
if isinstance(key, str):
|
|
3311
|
+
key = ZString(key)
|
|
3312
|
+
stack.append(obj.get(key))
|
|
3313
|
+
elif isinstance(obj, ZString):
|
|
3314
|
+
stack.append(obj[idx])
|
|
3315
|
+
else:
|
|
3316
|
+
# Fallback
|
|
3317
|
+
if obj is None:
|
|
3318
|
+
stack.append(None)
|
|
3319
|
+
else:
|
|
3320
|
+
raw_idx = idx.value if hasattr(idx, "value") else idx
|
|
3321
|
+
try:
|
|
3322
|
+
stack.append(obj[raw_idx])
|
|
3323
|
+
except Exception:
|
|
3324
|
+
stack.append(None)
|
|
1175
3325
|
except (IndexError, KeyError, TypeError):
|
|
1176
3326
|
stack.append(None)
|
|
1177
3327
|
|
|
3328
|
+
def _op_get_attr(_):
|
|
3329
|
+
attr = stack_pop() if stack else None
|
|
3330
|
+
obj = stack_pop() if stack else None
|
|
3331
|
+
if obj is None:
|
|
3332
|
+
stack_append(None)
|
|
3333
|
+
return
|
|
3334
|
+
attr_name = _unwrap(attr)
|
|
3335
|
+
try:
|
|
3336
|
+
if isinstance(obj, ZMap):
|
|
3337
|
+
key = attr_name
|
|
3338
|
+
if isinstance(key, str):
|
|
3339
|
+
key = ZString(key)
|
|
3340
|
+
stack_append(obj.get(key))
|
|
3341
|
+
elif isinstance(obj, dict):
|
|
3342
|
+
stack_append(obj.get(attr_name))
|
|
3343
|
+
else:
|
|
3344
|
+
stack_append(getattr(obj, attr_name, None))
|
|
3345
|
+
except Exception:
|
|
3346
|
+
stack_append(None)
|
|
3347
|
+
|
|
1178
3348
|
def _op_get_length(_):
|
|
1179
|
-
obj = stack.pop()
|
|
3349
|
+
obj = stack.pop() if stack else None
|
|
1180
3350
|
try:
|
|
1181
3351
|
if obj is None:
|
|
1182
3352
|
stack.append(0)
|
|
3353
|
+
elif isinstance(obj, ZList):
|
|
3354
|
+
stack.append(len(obj.elements))
|
|
3355
|
+
elif isinstance(obj, ZMap):
|
|
3356
|
+
stack.append(len(obj.pairs))
|
|
3357
|
+
elif isinstance(obj, ZString):
|
|
3358
|
+
stack.append(len(obj.value))
|
|
1183
3359
|
elif hasattr(obj, '__len__'):
|
|
1184
3360
|
stack.append(len(obj))
|
|
1185
3361
|
else:
|
|
@@ -1187,6 +3363,28 @@ class VM:
|
|
|
1187
3363
|
except (TypeError, AttributeError):
|
|
1188
3364
|
stack.append(0)
|
|
1189
3365
|
|
|
3366
|
+
def _op_read(_):
|
|
3367
|
+
path = stack.pop() if stack else None
|
|
3368
|
+
try:
|
|
3369
|
+
import os
|
|
3370
|
+
if path and os.path.exists(path):
|
|
3371
|
+
with open(path, 'r') as f:
|
|
3372
|
+
stack.append(f.read())
|
|
3373
|
+
else:
|
|
3374
|
+
stack.append(None)
|
|
3375
|
+
except:
|
|
3376
|
+
stack.append(None)
|
|
3377
|
+
|
|
3378
|
+
def _op_store_func(operand):
|
|
3379
|
+
name_idx, func_idx = operand
|
|
3380
|
+
name = const(name_idx)
|
|
3381
|
+
func = const(func_idx)
|
|
3382
|
+
_store(name, func)
|
|
3383
|
+
|
|
3384
|
+
def _op_print(_):
|
|
3385
|
+
val = stack_pop() if stack else None
|
|
3386
|
+
print(self._format_print_value(val))
|
|
3387
|
+
|
|
1190
3388
|
dispatch_table: Dict[str, Callable[[Any], Any]] = {
|
|
1191
3389
|
"LOAD_CONST": _op_load_const,
|
|
1192
3390
|
"LOAD_NAME": _op_load_name,
|
|
@@ -1196,7 +3394,7 @@ class VM:
|
|
|
1196
3394
|
"CALL_NAME": _op_call_name,
|
|
1197
3395
|
"CALL_TOP": _op_call_top,
|
|
1198
3396
|
"CALL_METHOD": _op_call_method,
|
|
1199
|
-
"ADD":
|
|
3397
|
+
"ADD": _op_add,
|
|
1200
3398
|
"SUB": _binary_op(lambda a, b: a - b),
|
|
1201
3399
|
"MUL": _binary_op(lambda a, b: a * b),
|
|
1202
3400
|
"DIV": _binary_op(lambda a, b: a / b if b != 0 else 0),
|
|
@@ -1214,470 +3412,1220 @@ class VM:
|
|
|
1214
3412
|
"JUMP_IF_FALSE": _op_jump_if_false,
|
|
1215
3413
|
"RETURN": _op_return,
|
|
1216
3414
|
"BUILD_LIST": _op_build_list,
|
|
1217
|
-
"BUILD_MAP": _op_build_map,
|
|
1218
|
-
"INDEX": _op_index,
|
|
1219
|
-
"
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
if
|
|
1242
|
-
if
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
prev_ip = ip
|
|
1248
|
-
ip += 1
|
|
1249
|
-
|
|
1250
|
-
# === GAS METERING ===
|
|
1251
|
-
if self.enable_gas_metering and self.gas_metering:
|
|
1252
|
-
# Determine gas cost parameters
|
|
1253
|
-
gas_kwargs = {}
|
|
1254
|
-
if op_name in ("BUILD_LIST", "BUILD_MAP"):
|
|
1255
|
-
gas_kwargs['count'] = operand if operand is not None else 0
|
|
1256
|
-
elif op_name == "MERKLE_ROOT":
|
|
1257
|
-
gas_kwargs['leaf_count'] = operand if operand is not None else 0
|
|
1258
|
-
elif op_name in ("CALL_NAME", "CALL_TOP", "CALL_METHOD"):
|
|
1259
|
-
if op_name == "CALL_NAME":
|
|
1260
|
-
gas_kwargs['arg_count'] = operand[1] if isinstance(operand, tuple) else 0
|
|
1261
|
-
elif op_name == "CALL_TOP":
|
|
1262
|
-
gas_kwargs['arg_count'] = operand if operand is not None else 0
|
|
1263
|
-
else:
|
|
1264
|
-
gas_kwargs['arg_count'] = operand[1] if isinstance(operand, tuple) else 0
|
|
1265
|
-
|
|
1266
|
-
# Consume gas for operation
|
|
1267
|
-
if not self.gas_metering.consume(op_name, **gas_kwargs):
|
|
1268
|
-
# Out of gas!
|
|
1269
|
-
if self.gas_metering.operation_count > self.gas_metering.max_operations:
|
|
1270
|
-
raise OperationLimitExceededError(
|
|
1271
|
-
self.gas_metering.operation_count,
|
|
1272
|
-
self.gas_metering.max_operations
|
|
1273
|
-
)
|
|
1274
|
-
else:
|
|
1275
|
-
raise OutOfGasError(
|
|
1276
|
-
self.gas_metering.gas_used,
|
|
1277
|
-
self.gas_metering.gas_limit,
|
|
1278
|
-
op_name
|
|
1279
|
-
)
|
|
1280
|
-
|
|
1281
|
-
handler = dispatch_table.get(op_name)
|
|
1282
|
-
if handler is not None:
|
|
1283
|
-
if op_name in async_dispatch_ops:
|
|
1284
|
-
await handler(operand)
|
|
1285
|
-
else:
|
|
1286
|
-
handler(operand)
|
|
1287
|
-
if not running:
|
|
1288
|
-
break
|
|
1289
|
-
continue
|
|
1290
|
-
|
|
1291
|
-
# --- Basic Stack Ops ---
|
|
1292
|
-
if op_name == "LOAD_CONST":
|
|
1293
|
-
stack.append(const(operand))
|
|
1294
|
-
elif op_name == "LOAD_NAME":
|
|
1295
|
-
name = const(operand)
|
|
1296
|
-
stack.append(_resolve(name))
|
|
1297
|
-
elif op_name == "STORE_NAME":
|
|
1298
|
-
name = const(operand)
|
|
1299
|
-
val = stack.pop() if stack else None
|
|
1300
|
-
_store(name, val)
|
|
1301
|
-
if self.use_memory_manager and val is not None:
|
|
1302
|
-
self._allocate_managed(val, name=name)
|
|
1303
|
-
elif op_name == "POP":
|
|
1304
|
-
if stack: stack.pop()
|
|
1305
|
-
elif op_name == "DUP":
|
|
1306
|
-
if stack: stack.append(stack[-1])
|
|
1307
|
-
elif op_name == "PRINT":
|
|
1308
|
-
val = stack.pop() if stack else None
|
|
1309
|
-
print(val)
|
|
1310
|
-
|
|
1311
|
-
# --- Function/Closure Ops ---
|
|
1312
|
-
elif op_name == "STORE_FUNC":
|
|
1313
|
-
name_idx, func_idx = operand
|
|
1314
|
-
name = const(name_idx)
|
|
1315
|
-
func_desc = const(func_idx)
|
|
1316
|
-
# Create func descriptor, capturing current VM as parent
|
|
1317
|
-
func_desc_copy = dict(func_desc) if isinstance(func_desc, dict) else {"bytecode": func_desc}
|
|
1318
|
-
closure_snapshot = {}
|
|
1319
|
-
# Snapshot current environment (excluding internal keys)
|
|
1320
|
-
for key, value in self.env.items():
|
|
1321
|
-
if isinstance(key, str) and key.startswith("_"):
|
|
1322
|
-
continue
|
|
1323
|
-
closure_snapshot[key] = value
|
|
1324
|
-
# Include existing closure cells if present
|
|
1325
|
-
for key, cell in self._closure_cells.items():
|
|
1326
|
-
if key not in closure_snapshot:
|
|
1327
|
-
closure_snapshot[key] = cell.value
|
|
1328
|
-
if closure_snapshot:
|
|
1329
|
-
func_desc_copy["closure_snapshot"] = closure_snapshot
|
|
1330
|
-
func_desc_copy["parent_vm"] = self
|
|
1331
|
-
self.env[name] = func_desc_copy
|
|
1332
|
-
|
|
1333
|
-
elif op_name == "CALL_NAME":
|
|
1334
|
-
name_idx, arg_count = operand
|
|
1335
|
-
func_name = const(name_idx)
|
|
1336
|
-
args = [stack.pop() for _ in range(arg_count)][::-1] if arg_count else []
|
|
1337
|
-
fn = _resolve(func_name) or self.builtins.get(func_name)
|
|
1338
|
-
if fn is None:
|
|
1339
|
-
res = self._call_fallback_builtin(func_name, args)
|
|
3415
|
+
"BUILD_MAP": _op_build_map,
|
|
3416
|
+
"INDEX": _op_index,
|
|
3417
|
+
"GET_ATTR": _op_get_attr,
|
|
3418
|
+
"GET_LENGTH": _op_get_length,
|
|
3419
|
+
"READ": _op_read,
|
|
3420
|
+
"STORE_FUNC": _op_store_func,
|
|
3421
|
+
"PRINT": _op_print,
|
|
3422
|
+
}
|
|
3423
|
+
async_dispatch_ops = {"CALL_NAME", "CALL_TOP", "CALL_METHOD"}
|
|
3424
|
+
gas_kwarg_ops = {"BUILD_LIST", "BUILD_MAP", "MERKLE_ROOT", "CALL_NAME", "CALL_TOP", "CALL_METHOD", "CALL_BUILTIN"}
|
|
3425
|
+
|
|
3426
|
+
prepared_instrs: List[Tuple[str, Any, Optional[Callable[[Any], Any]], bool, int]] = []
|
|
3427
|
+
has_async_ops = False
|
|
3428
|
+
has_loop_ops = False
|
|
3429
|
+
loop_ops = {"JUMP", "JUMP_IF_FALSE", "JUMP_IF_TRUE", "FOR_ITER"}
|
|
3430
|
+
for instr in instrs:
|
|
3431
|
+
op_name, operand = instr
|
|
3432
|
+
handler = dispatch_table.get(op_name)
|
|
3433
|
+
is_async = op_name in async_dispatch_ops
|
|
3434
|
+
if is_async:
|
|
3435
|
+
has_async_ops = True
|
|
3436
|
+
if op_name in loop_ops:
|
|
3437
|
+
has_loop_ops = True
|
|
3438
|
+
gas_kind = 0
|
|
3439
|
+
if op_name in gas_kwarg_ops:
|
|
3440
|
+
if op_name in ("BUILD_LIST", "BUILD_MAP"):
|
|
3441
|
+
gas_kind = 1
|
|
3442
|
+
elif op_name == "MERKLE_ROOT":
|
|
3443
|
+
gas_kind = 2
|
|
1340
3444
|
else:
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
elif op_name == "CALL_TOP":
|
|
1345
|
-
arg_count = operand
|
|
1346
|
-
args = [stack.pop() for _ in range(arg_count)][::-1] if arg_count else []
|
|
1347
|
-
fn_obj = stack.pop() if stack else None
|
|
1348
|
-
res = await self._invoke_callable_or_funcdesc(fn_obj, args)
|
|
1349
|
-
stack.append(res)
|
|
1350
|
-
|
|
1351
|
-
# --- Arithmetic & Logic ---
|
|
1352
|
-
elif op_name == "ADD":
|
|
1353
|
-
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
1354
|
-
# Auto-unwrap evaluator objects
|
|
1355
|
-
if hasattr(a, 'value'): a = a.value
|
|
1356
|
-
if hasattr(b, 'value'): b = b.value
|
|
1357
|
-
stack.append(a + b)
|
|
1358
|
-
elif op_name == "SUB":
|
|
1359
|
-
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
1360
|
-
if hasattr(a, 'value'): a = a.value
|
|
1361
|
-
if hasattr(b, 'value'): b = b.value
|
|
1362
|
-
stack.append(a - b)
|
|
1363
|
-
elif op_name == "MUL":
|
|
1364
|
-
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
1365
|
-
if hasattr(a, 'value'): a = a.value
|
|
1366
|
-
if hasattr(b, 'value'): b = b.value
|
|
1367
|
-
stack.append(a * b)
|
|
1368
|
-
elif op_name == "DIV":
|
|
1369
|
-
b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
|
|
1370
|
-
if hasattr(a, 'value'): a = a.value
|
|
1371
|
-
if hasattr(b, 'value'): b = b.value
|
|
1372
|
-
stack.append(a / b if b != 0 else 0)
|
|
1373
|
-
elif op_name == "MOD":
|
|
1374
|
-
b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
|
|
1375
|
-
stack.append(a % b if b != 0 else 0)
|
|
1376
|
-
elif op_name == "POW":
|
|
1377
|
-
b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
|
|
1378
|
-
stack.append(a ** b)
|
|
1379
|
-
elif op_name == "NEG":
|
|
1380
|
-
a = stack.pop() if stack else 0
|
|
1381
|
-
stack.append(-a)
|
|
1382
|
-
elif op_name == "EQ":
|
|
1383
|
-
b = stack.pop() if stack else None; a = stack.pop() if stack else None
|
|
1384
|
-
stack.append(a == b)
|
|
1385
|
-
elif op_name == "NEQ":
|
|
1386
|
-
b = stack.pop() if stack else None; a = stack.pop() if stack else None
|
|
1387
|
-
stack.append(a != b)
|
|
1388
|
-
elif op_name == "LT":
|
|
1389
|
-
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
1390
|
-
stack.append(a < b)
|
|
1391
|
-
elif op_name == "GT":
|
|
1392
|
-
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
1393
|
-
stack.append(a > b)
|
|
1394
|
-
elif op_name == "LTE":
|
|
1395
|
-
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
1396
|
-
stack.append(a <= b)
|
|
1397
|
-
elif op_name == "GTE":
|
|
1398
|
-
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
1399
|
-
stack.append(a >= b)
|
|
1400
|
-
elif op_name == "NOT":
|
|
1401
|
-
a = stack.pop() if stack else False
|
|
1402
|
-
stack.append(not a)
|
|
3445
|
+
gas_kind = 3
|
|
3446
|
+
prepared_instrs.append((op_name, operand, handler, is_async, gas_kind))
|
|
1403
3447
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
3448
|
+
# 3. Execution Loop
|
|
3449
|
+
prev_ip = None
|
|
3450
|
+
try_stack: List[int] = []
|
|
3451
|
+
auto_fast_loop = (
|
|
3452
|
+
len(prepared_instrs) >= getattr(self, "fast_loop_threshold", 512)
|
|
3453
|
+
or has_loop_ops
|
|
3454
|
+
)
|
|
3455
|
+
fast_loop_allowed = (
|
|
3456
|
+
(self.enable_fast_loop or auto_fast_loop)
|
|
3457
|
+
and not profile_ops
|
|
3458
|
+
and not gas_enabled # Never skip gas metering (including gas_light)
|
|
3459
|
+
and not trace_interval
|
|
3460
|
+
and trace_ip_range is None
|
|
3461
|
+
and not trace_loads_active
|
|
3462
|
+
and not trace_calls_active
|
|
3463
|
+
and not trace_targets_active
|
|
3464
|
+
and not self.enable_profiling
|
|
3465
|
+
)
|
|
3466
|
+
missing_handlers = any(handler is None for _, _, handler, _, _ in prepared_instrs)
|
|
3467
|
+
if self.enable_fast_loop or auto_fast_loop:
|
|
3468
|
+
reasons = []
|
|
3469
|
+
if auto_fast_loop and not self.enable_fast_loop:
|
|
3470
|
+
reasons.append("auto")
|
|
3471
|
+
if profile_ops:
|
|
3472
|
+
reasons.append("opcode_profile")
|
|
3473
|
+
if gas_enabled and not gas_light:
|
|
3474
|
+
reasons.append("gas")
|
|
3475
|
+
if trace_interval:
|
|
3476
|
+
reasons.append("trace_interval")
|
|
3477
|
+
if trace_ip_range is not None:
|
|
3478
|
+
reasons.append("trace_ip_range")
|
|
3479
|
+
if trace_loads_active:
|
|
3480
|
+
reasons.append("trace_loads")
|
|
3481
|
+
if trace_calls_active:
|
|
3482
|
+
reasons.append("trace_calls")
|
|
3483
|
+
if trace_targets_active:
|
|
3484
|
+
reasons.append("trace_targets")
|
|
3485
|
+
if self.enable_profiling:
|
|
3486
|
+
reasons.append("profiler")
|
|
3487
|
+
if missing_handlers:
|
|
3488
|
+
reasons.append("missing_handlers")
|
|
3489
|
+
self._fast_loop_stats = {
|
|
3490
|
+
"used": False,
|
|
3491
|
+
"reason": ",".join(reasons) if reasons else "conditions",
|
|
3492
|
+
"auto": auto_fast_loop,
|
|
3493
|
+
"threshold": getattr(self, "fast_loop_threshold", 512),
|
|
3494
|
+
"instr_count": len(prepared_instrs),
|
|
3495
|
+
}
|
|
1412
3496
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
3497
|
+
if fast_loop_allowed and not missing_handlers:
|
|
3498
|
+
self._fast_loop_stats = {
|
|
3499
|
+
"used": True,
|
|
3500
|
+
"reason": "",
|
|
3501
|
+
"auto": auto_fast_loop,
|
|
3502
|
+
"threshold": getattr(self, "fast_loop_threshold", 512),
|
|
3503
|
+
"instr_count": len(prepared_instrs),
|
|
3504
|
+
}
|
|
3505
|
+
local_prepared = prepared_instrs
|
|
3506
|
+
while running and ip < len(local_prepared):
|
|
3507
|
+
op_name, operand, handler, is_async, _gas_kind = local_prepared[ip]
|
|
3508
|
+
prev_ip = ip
|
|
3509
|
+
ip += 1
|
|
3510
|
+
if is_async:
|
|
3511
|
+
await handler(operand)
|
|
3512
|
+
else:
|
|
3513
|
+
handler(operand)
|
|
3514
|
+
if not running:
|
|
3515
|
+
break
|
|
3516
|
+
if not running:
|
|
3517
|
+
return return_value
|
|
3518
|
+
while running and ip < len(prepared_instrs):
|
|
3519
|
+
try:
|
|
3520
|
+
current_ip = ip
|
|
3521
|
+
op_name, operand, handler, is_async, gas_kind = prepared_instrs[current_ip]
|
|
3522
|
+
|
|
3523
|
+
if profile_ops:
|
|
3524
|
+
opcode_counts[op_name] = opcode_counts.get(op_name, 0) + 1
|
|
3525
|
+
|
|
3526
|
+
if debug: print(f"[VM SL] ip={ip} op={op} operand={operand} stack={stack.snapshot()}")
|
|
3527
|
+
|
|
3528
|
+
# Profile instruction (if enabled) - start timing
|
|
3529
|
+
instr_start_time = None
|
|
3530
|
+
if self.enable_profiling and self.profiler and self.profiler.enabled:
|
|
3531
|
+
if self.profiler.level in (ProfilingLevel.DETAILED, ProfilingLevel.FULL):
|
|
3532
|
+
instr_start_time = time.perf_counter()
|
|
3533
|
+
# OPTIMIZATION: Use stack.sp instead of len(stack) to avoid 500k function calls
|
|
3534
|
+
self.profiler.record_instruction(current_ip, op_name, operand, prev_ip, stack.sp)
|
|
3535
|
+
|
|
3536
|
+
prev_ip = current_ip
|
|
3537
|
+
ip += 1
|
|
3538
|
+
|
|
3539
|
+
if trace_interval > 0:
|
|
3540
|
+
trace_counter += 1
|
|
3541
|
+
if trace_counter % trace_interval == 0:
|
|
3542
|
+
try:
|
|
3543
|
+
stack_size = stack.sp # OPTIMIZATION: Direct attribute access
|
|
3544
|
+
except Exception:
|
|
3545
|
+
stack_size = -1
|
|
3546
|
+
print(f"[VM TRACE] async ip={current_ip} op={op_name} stack={stack_size}")
|
|
3547
|
+
|
|
3548
|
+
# === GAS METERING ===
|
|
3549
|
+
if gas_enabled:
|
|
3550
|
+
if gas_kind == 1:
|
|
3551
|
+
count = operand if operand is not None else 0
|
|
3552
|
+
if gas_light:
|
|
3553
|
+
ok = gas_consume_light(self.gas_light_cost)
|
|
3554
|
+
else:
|
|
3555
|
+
ok = gas_consume(op_name, count=count)
|
|
3556
|
+
elif gas_kind == 2:
|
|
3557
|
+
leaf_count = operand if operand is not None else 0
|
|
3558
|
+
if gas_light:
|
|
3559
|
+
ok = gas_consume_light(self.gas_light_cost)
|
|
3560
|
+
else:
|
|
3561
|
+
ok = gas_consume(op_name, leaf_count=leaf_count)
|
|
3562
|
+
elif gas_kind == 3:
|
|
3563
|
+
if op_name == "CALL_NAME":
|
|
3564
|
+
arg_count = operand[1] if isinstance(operand, tuple) else 0
|
|
3565
|
+
elif op_name == "CALL_TOP":
|
|
3566
|
+
arg_count = operand if operand is not None else 0
|
|
3567
|
+
else:
|
|
3568
|
+
arg_count = operand[1] if isinstance(operand, tuple) else 0
|
|
3569
|
+
if gas_light:
|
|
3570
|
+
ok = gas_consume_light(self.gas_light_cost)
|
|
3571
|
+
else:
|
|
3572
|
+
ok = gas_consume(op_name, arg_count=arg_count)
|
|
1436
3573
|
else:
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
3574
|
+
if gas_light:
|
|
3575
|
+
ok = gas_consume_light(self.gas_light_cost)
|
|
3576
|
+
else:
|
|
3577
|
+
ok = gas_consume(op_name)
|
|
3578
|
+
|
|
3579
|
+
# Consume gas for operation
|
|
3580
|
+
if not ok:
|
|
3581
|
+
# Out of gas!
|
|
3582
|
+
if gas_metering.operation_count > gas_metering.max_operations:
|
|
3583
|
+
raise OperationLimitExceededError(
|
|
3584
|
+
gas_metering.operation_count,
|
|
3585
|
+
gas_metering.max_operations
|
|
3586
|
+
)
|
|
3587
|
+
else:
|
|
3588
|
+
raise OutOfGasError(
|
|
3589
|
+
gas_metering.gas_used,
|
|
3590
|
+
gas_metering.gas_limit,
|
|
3591
|
+
op_name
|
|
3592
|
+
)
|
|
3593
|
+
|
|
3594
|
+
if trace_ip_range and trace_ip_range[0] <= current_ip <= trace_ip_range[1]:
|
|
3595
|
+
op_detail = op_name
|
|
3596
|
+
if op_name in ("LOAD_NAME", "STORE_NAME"):
|
|
3597
|
+
try:
|
|
3598
|
+
op_detail = f"{op_name}({const(operand)})"
|
|
3599
|
+
except Exception:
|
|
3600
|
+
op_detail = op_name
|
|
3601
|
+
elif op_name == "LOAD_CONST":
|
|
3602
|
+
try:
|
|
3603
|
+
op_detail = f"{op_name}({const(operand)})"
|
|
3604
|
+
except Exception:
|
|
3605
|
+
op_detail = op_name
|
|
3606
|
+
print(f"[VM TRACE] ip={current_ip} op={op_detail} pre_stack={len(stack)}")
|
|
3607
|
+
if handler is not None:
|
|
3608
|
+
if is_async:
|
|
3609
|
+
await handler(operand)
|
|
3610
|
+
else:
|
|
3611
|
+
handler(operand)
|
|
3612
|
+
if trace_ip_range and trace_ip_range[0] <= current_ip <= trace_ip_range[1]:
|
|
3613
|
+
print(f"[VM TRACE] ip={current_ip} op={op_detail} post_stack={len(stack)}")
|
|
3614
|
+
if not running:
|
|
3615
|
+
break
|
|
3616
|
+
continue
|
|
3617
|
+
|
|
3618
|
+
# --- Basic Stack Ops ---
|
|
3619
|
+
if op_name == "LOAD_CONST":
|
|
3620
|
+
stack.append(const(operand))
|
|
3621
|
+
elif op_name == "LOAD_NAME":
|
|
3622
|
+
name = const(operand)
|
|
3623
|
+
stack.append(_resolve(name))
|
|
3624
|
+
elif op_name == "STORE_NAME":
|
|
3625
|
+
name = const(operand)
|
|
3626
|
+
val = stack.pop() if stack else None
|
|
3627
|
+
_store(name, val)
|
|
3628
|
+
if self.use_memory_manager and val is not None:
|
|
3629
|
+
self._allocate_managed(val, name=name)
|
|
3630
|
+
elif op_name == "POP":
|
|
3631
|
+
if stack: stack.pop()
|
|
3632
|
+
elif op_name == "DUP":
|
|
3633
|
+
if stack: stack.append(stack[-1])
|
|
3634
|
+
elif op_name == "PRINT":
|
|
3635
|
+
val = stack.pop() if stack else None
|
|
3636
|
+
print(self._format_print_value(val))
|
|
3637
|
+
|
|
3638
|
+
# --- Function/Closure Ops ---
|
|
3639
|
+
elif op_name == "STORE_FUNC":
|
|
3640
|
+
name_idx, func_idx = operand
|
|
3641
|
+
name = const(name_idx)
|
|
3642
|
+
func_desc = const(func_idx)
|
|
3643
|
+
# Create func descriptor, capturing current VM as parent
|
|
3644
|
+
func_desc_copy = dict(func_desc) if isinstance(func_desc, dict) else {"bytecode": func_desc}
|
|
3645
|
+
closure_snapshot = {}
|
|
3646
|
+
# Snapshot current environment (excluding internal keys)
|
|
3647
|
+
for key, value in self.env.items():
|
|
3648
|
+
if isinstance(key, str) and key.startswith("_"):
|
|
3649
|
+
continue
|
|
3650
|
+
closure_snapshot[key] = value
|
|
3651
|
+
# Include existing closure cells if present
|
|
3652
|
+
for key, cell in self._closure_cells.items():
|
|
3653
|
+
if key not in closure_snapshot:
|
|
3654
|
+
closure_snapshot[key] = cell.value
|
|
3655
|
+
if closure_snapshot:
|
|
3656
|
+
func_desc_copy["closure_snapshot"] = closure_snapshot
|
|
3657
|
+
func_desc_copy["parent_vm"] = self
|
|
3658
|
+
self.env[name] = func_desc_copy
|
|
3659
|
+
self._bump_env_version(name, func_desc_copy)
|
|
3660
|
+
|
|
3661
|
+
elif op_name == "LOAD_REG":
|
|
3662
|
+
reg, const_idx = operand
|
|
3663
|
+
value = const(const_idx)
|
|
3664
|
+
if not hasattr(self, "_jit_registers"):
|
|
3665
|
+
self._jit_registers = {}
|
|
3666
|
+
self._jit_registers[reg] = value
|
|
3667
|
+
|
|
3668
|
+
elif op_name == "LOAD_VAR_REG":
|
|
3669
|
+
reg, name_idx = operand
|
|
3670
|
+
name = const(name_idx)
|
|
3671
|
+
value = _resolve(name)
|
|
3672
|
+
if not hasattr(self, "_jit_registers"):
|
|
3673
|
+
self._jit_registers = {}
|
|
3674
|
+
self._jit_registers[reg] = value
|
|
3675
|
+
|
|
3676
|
+
elif op_name == "STORE_REG":
|
|
3677
|
+
reg, name_idx = operand
|
|
3678
|
+
name = const(name_idx)
|
|
3679
|
+
value = getattr(self, "_jit_registers", {}).get(reg)
|
|
3680
|
+
_store(name, value)
|
|
3681
|
+
|
|
3682
|
+
elif op_name == "MOV_REG":
|
|
3683
|
+
dest, src = operand
|
|
3684
|
+
if not hasattr(self, "_jit_registers"):
|
|
3685
|
+
self._jit_registers = {}
|
|
3686
|
+
self._jit_registers[dest] = self._jit_registers.get(src)
|
|
3687
|
+
|
|
3688
|
+
elif op_name == "PUSH_REG":
|
|
3689
|
+
reg = operand if not isinstance(operand, (list, tuple)) else operand[0]
|
|
3690
|
+
value = getattr(self, "_jit_registers", {}).get(reg)
|
|
3691
|
+
stack.append(value)
|
|
3692
|
+
|
|
3693
|
+
elif op_name in ("ADD_REG", "SUB_REG", "MUL_REG", "DIV_REG", "MOD_REG"):
|
|
3694
|
+
dest, src1, src2 = operand
|
|
3695
|
+
regs = getattr(self, "_jit_registers", {})
|
|
3696
|
+
v1 = regs.get(src1)
|
|
3697
|
+
v2 = regs.get(src2)
|
|
3698
|
+
if op_name == "ADD_REG":
|
|
3699
|
+
res = v1 + v2
|
|
3700
|
+
elif op_name == "SUB_REG":
|
|
3701
|
+
res = v1 - v2
|
|
3702
|
+
elif op_name == "MUL_REG":
|
|
3703
|
+
res = v1 * v2
|
|
3704
|
+
elif op_name == "DIV_REG":
|
|
3705
|
+
res = v1 / v2 if v2 != 0 else 0
|
|
3706
|
+
else:
|
|
3707
|
+
res = v1 % v2 if v2 != 0 else 0
|
|
3708
|
+
if not hasattr(self, "_jit_registers"):
|
|
3709
|
+
self._jit_registers = {}
|
|
3710
|
+
self._jit_registers[dest] = res
|
|
3711
|
+
|
|
3712
|
+
elif op_name == "POW_REG":
|
|
3713
|
+
dest, src1, src2 = operand
|
|
3714
|
+
regs = getattr(self, "_jit_registers", {})
|
|
3715
|
+
v1 = regs.get(src1)
|
|
3716
|
+
v2 = regs.get(src2)
|
|
3717
|
+
res = v1 ** v2
|
|
3718
|
+
if not hasattr(self, "_jit_registers"):
|
|
3719
|
+
self._jit_registers = {}
|
|
3720
|
+
self._jit_registers[dest] = res
|
|
3721
|
+
|
|
3722
|
+
elif op_name == "NEG_REG":
|
|
3723
|
+
dest, src = operand
|
|
3724
|
+
regs = getattr(self, "_jit_registers", {})
|
|
3725
|
+
v1 = regs.get(src)
|
|
3726
|
+
res = -v1
|
|
3727
|
+
if not hasattr(self, "_jit_registers"):
|
|
3728
|
+
self._jit_registers = {}
|
|
3729
|
+
self._jit_registers[dest] = res
|
|
3730
|
+
|
|
3731
|
+
elif op_name in ("EQ_REG", "NEQ_REG", "LT_REG"):
|
|
3732
|
+
dest, src1, src2 = operand
|
|
3733
|
+
regs = getattr(self, "_jit_registers", {})
|
|
3734
|
+
v1 = regs.get(src1)
|
|
3735
|
+
v2 = regs.get(src2)
|
|
3736
|
+
if op_name == "EQ_REG":
|
|
3737
|
+
res = v1 == v2
|
|
3738
|
+
elif op_name == "NEQ_REG":
|
|
3739
|
+
res = v1 != v2
|
|
3740
|
+
else:
|
|
3741
|
+
res = v1 < v2
|
|
3742
|
+
if not hasattr(self, "_jit_registers"):
|
|
3743
|
+
self._jit_registers = {}
|
|
3744
|
+
self._jit_registers[dest] = res
|
|
3745
|
+
|
|
3746
|
+
elif op_name in ("GT_REG", "LTE_REG", "GTE_REG"):
|
|
3747
|
+
dest, src1, src2 = operand
|
|
3748
|
+
regs = getattr(self, "_jit_registers", {})
|
|
3749
|
+
v1 = regs.get(src1)
|
|
3750
|
+
v2 = regs.get(src2)
|
|
3751
|
+
if op_name == "GT_REG":
|
|
3752
|
+
res = v1 > v2
|
|
3753
|
+
elif op_name == "LTE_REG":
|
|
3754
|
+
res = v1 <= v2
|
|
3755
|
+
else:
|
|
3756
|
+
res = v1 >= v2
|
|
3757
|
+
if not hasattr(self, "_jit_registers"):
|
|
3758
|
+
self._jit_registers = {}
|
|
3759
|
+
self._jit_registers[dest] = res
|
|
3760
|
+
|
|
3761
|
+
elif op_name in ("AND_REG", "OR_REG"):
|
|
3762
|
+
dest, src1, src2 = operand
|
|
3763
|
+
regs = getattr(self, "_jit_registers", {})
|
|
3764
|
+
v1 = regs.get(src1)
|
|
3765
|
+
v2 = regs.get(src2)
|
|
3766
|
+
res = v1 and v2 if op_name == "AND_REG" else v1 or v2
|
|
3767
|
+
if not hasattr(self, "_jit_registers"):
|
|
3768
|
+
self._jit_registers = {}
|
|
3769
|
+
self._jit_registers[dest] = res
|
|
3770
|
+
|
|
3771
|
+
elif op_name == "NOT_REG":
|
|
3772
|
+
dest, src = operand
|
|
3773
|
+
regs = getattr(self, "_jit_registers", {})
|
|
3774
|
+
v1 = regs.get(src)
|
|
3775
|
+
res = not v1
|
|
3776
|
+
if not hasattr(self, "_jit_registers"):
|
|
3777
|
+
self._jit_registers = {}
|
|
3778
|
+
self._jit_registers[dest] = res
|
|
3779
|
+
|
|
3780
|
+
elif op_name == "POP_REG":
|
|
3781
|
+
reg = operand if not isinstance(operand, (list, tuple)) else operand[0]
|
|
3782
|
+
value = stack.pop() if stack else None
|
|
3783
|
+
if not hasattr(self, "_jit_registers"):
|
|
3784
|
+
self._jit_registers = {}
|
|
3785
|
+
self._jit_registers[reg] = value
|
|
3786
|
+
|
|
3787
|
+
elif op_name == "SPAWN_TASK":
|
|
3788
|
+
task_handle = None
|
|
3789
|
+
if isinstance(operand, tuple) and operand[0] == "CALL":
|
|
3790
|
+
fn_name = operand[1]; arg_count = operand[2]
|
|
3791
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1]
|
|
3792
|
+
fn = self.builtins.get(fn_name) or self.env.get(fn_name)
|
|
3793
|
+
coro = self._to_coro(fn, args)
|
|
3794
|
+
if self.async_optimizer:
|
|
3795
|
+
coro = self.async_optimizer.spawn(coro)
|
|
1454
3796
|
task = asyncio.create_task(coro)
|
|
3797
|
+
self._task_counter += 1
|
|
3798
|
+
tid = f"task_{self._task_counter}"
|
|
3799
|
+
self._tasks[tid] = task
|
|
3800
|
+
task_handle = tid
|
|
1455
3801
|
else:
|
|
3802
|
+
arg_count = int(operand) if operand is not None else 0
|
|
3803
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
3804
|
+
callable_obj = stack.pop() if stack else None
|
|
3805
|
+
coro = self._to_coro(callable_obj, args)
|
|
3806
|
+
if self.async_optimizer:
|
|
3807
|
+
coro = self.async_optimizer.spawn(coro)
|
|
1456
3808
|
task = asyncio.create_task(coro)
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
task_handle
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
3809
|
+
self._task_counter += 1
|
|
3810
|
+
tid = f"task_{self._task_counter}"
|
|
3811
|
+
self._tasks[tid] = task
|
|
3812
|
+
task_handle = tid
|
|
3813
|
+
stack.append(task_handle)
|
|
3814
|
+
|
|
3815
|
+
elif op_name == "TASK_JOIN":
|
|
3816
|
+
task_ref = stack.pop() if stack else None
|
|
3817
|
+
if isinstance(task_ref, str) and task_ref in self._tasks:
|
|
3818
|
+
res = await self._tasks[task_ref]
|
|
3819
|
+
stack.append(res)
|
|
3820
|
+
elif asyncio.iscoroutine(task_ref) or isinstance(task_ref, asyncio.Future):
|
|
3821
|
+
res = await task_ref
|
|
3822
|
+
stack.append(res)
|
|
3823
|
+
else:
|
|
3824
|
+
stack.append(task_ref)
|
|
3825
|
+
|
|
3826
|
+
elif op_name == "TASK_RESULT":
|
|
3827
|
+
task_ref = stack.pop() if stack else None
|
|
3828
|
+
if isinstance(task_ref, str) and task_ref in self._tasks:
|
|
3829
|
+
res = await self._tasks[task_ref]
|
|
3830
|
+
stack.append(res)
|
|
3831
|
+
elif asyncio.iscoroutine(task_ref) or isinstance(task_ref, asyncio.Future):
|
|
3832
|
+
res = await task_ref
|
|
3833
|
+
stack.append(res)
|
|
3834
|
+
else:
|
|
3835
|
+
stack.append(task_ref)
|
|
3836
|
+
|
|
3837
|
+
elif op_name == "LOCK_ACQUIRE":
|
|
3838
|
+
if not hasattr(self, "_locks"):
|
|
3839
|
+
self._locks = {}
|
|
3840
|
+
key = const(operand) if operand is not None else (stack.pop() if stack else None)
|
|
3841
|
+
key = _unwrap(key)
|
|
3842
|
+
lock = self._locks.get(key)
|
|
3843
|
+
if lock is None:
|
|
3844
|
+
import threading
|
|
3845
|
+
lock = threading.Lock()
|
|
3846
|
+
self._locks[key] = lock
|
|
3847
|
+
lock.acquire()
|
|
3848
|
+
|
|
3849
|
+
elif op_name == "LOCK_RELEASE":
|
|
3850
|
+
if not hasattr(self, "_locks"):
|
|
3851
|
+
self._locks = {}
|
|
3852
|
+
key = const(operand) if operand is not None else (stack.pop() if stack else None)
|
|
3853
|
+
key = _unwrap(key)
|
|
3854
|
+
lock = self._locks.get(key)
|
|
3855
|
+
if lock:
|
|
3856
|
+
lock.release()
|
|
3857
|
+
|
|
3858
|
+
elif op_name == "BARRIER":
|
|
3859
|
+
barrier_obj = stack.pop() if stack else None
|
|
3860
|
+
timeout = const(operand) if operand is not None else None
|
|
3861
|
+
if hasattr(barrier_obj, "wait"):
|
|
3862
|
+
try:
|
|
3863
|
+
res = barrier_obj.wait(timeout=timeout) if timeout is not None else barrier_obj.wait()
|
|
3864
|
+
except Exception as exc:
|
|
3865
|
+
res = exc
|
|
3866
|
+
stack.append(res)
|
|
3867
|
+
else:
|
|
3868
|
+
stack.append(None)
|
|
3869
|
+
|
|
3870
|
+
elif op_name == "ATOMIC_ADD":
|
|
3871
|
+
delta = stack.pop() if stack else 0
|
|
3872
|
+
key = stack.pop() if operand is None else const(operand)
|
|
3873
|
+
key = _unwrap(key)
|
|
3874
|
+
if not hasattr(self, "_atomic_lock"):
|
|
3875
|
+
import threading
|
|
3876
|
+
self._atomic_lock = threading.Lock()
|
|
3877
|
+
if "_atomic_state" not in self.env:
|
|
3878
|
+
self.env["_atomic_state"] = {}
|
|
3879
|
+
with self._atomic_lock:
|
|
3880
|
+
current = self.env["_atomic_state"].get(key, 0)
|
|
3881
|
+
new_val = current + delta
|
|
3882
|
+
self.env["_atomic_state"][key] = new_val
|
|
3883
|
+
stack.append(new_val)
|
|
3884
|
+
|
|
3885
|
+
elif op_name == "ATOMIC_CAS":
|
|
3886
|
+
new_val = stack.pop() if stack else None
|
|
3887
|
+
expected = stack.pop() if stack else None
|
|
3888
|
+
key = stack.pop() if operand is None else const(operand)
|
|
3889
|
+
key = _unwrap(key)
|
|
3890
|
+
if not hasattr(self, "_atomic_lock"):
|
|
3891
|
+
import threading
|
|
3892
|
+
self._atomic_lock = threading.Lock()
|
|
3893
|
+
if "_atomic_state" not in self.env:
|
|
3894
|
+
self.env["_atomic_state"] = {}
|
|
3895
|
+
with self._atomic_lock:
|
|
3896
|
+
current = self.env["_atomic_state"].get(key, None)
|
|
3897
|
+
ok = current == expected
|
|
3898
|
+
if ok:
|
|
3899
|
+
self.env["_atomic_state"][key] = new_val
|
|
3900
|
+
stack.append(ok)
|
|
3901
|
+
|
|
3902
|
+
elif op_name == "FOR_ITER":
|
|
3903
|
+
target = int(operand) if operand is not None else ip
|
|
3904
|
+
it = stack.pop() if stack else None
|
|
3905
|
+
if it is None:
|
|
3906
|
+
ip = target
|
|
3907
|
+
else:
|
|
3908
|
+
try:
|
|
3909
|
+
iterator = iter(it)
|
|
3910
|
+
value = next(iterator)
|
|
3911
|
+
stack.append(iterator)
|
|
3912
|
+
stack.append(value)
|
|
3913
|
+
except StopIteration:
|
|
3914
|
+
ip = target
|
|
3915
|
+
|
|
3916
|
+
elif op_name == "CALL_NAME":
|
|
3917
|
+
name_idx, arg_count = operand
|
|
3918
|
+
func_name = const(name_idx)
|
|
3919
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
3920
|
+
fn = _resolve(func_name) or self.builtins.get(func_name)
|
|
3921
|
+
if fn is None:
|
|
3922
|
+
res = self._call_fallback_builtin(func_name, args)
|
|
3923
|
+
else:
|
|
3924
|
+
res = await self._invoke_callable_or_funcdesc(fn, args)
|
|
3925
|
+
stack.append(res)
|
|
3926
|
+
|
|
3927
|
+
elif op_name == "CALL_BUILTIN":
|
|
3928
|
+
name_idx, arg_count = operand if isinstance(operand, (list, tuple)) else (operand, 0)
|
|
3929
|
+
func_name = const(name_idx)
|
|
3930
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
3931
|
+
fn = self.builtins.get(func_name)
|
|
3932
|
+
if fn is None:
|
|
3933
|
+
res = self._call_fallback_builtin(func_name, args)
|
|
3934
|
+
else:
|
|
3935
|
+
res = await self._invoke_callable_or_funcdesc(fn, args)
|
|
3936
|
+
stack.append(res)
|
|
3937
|
+
|
|
3938
|
+
elif op_name == "CALL_FUNC_CONST":
|
|
3939
|
+
if isinstance(operand, (list, tuple)):
|
|
3940
|
+
func_idx = operand[0]
|
|
3941
|
+
arg_count = operand[1] if len(operand) > 1 else 0
|
|
3942
|
+
else:
|
|
3943
|
+
func_idx = operand
|
|
3944
|
+
arg_count = 0
|
|
3945
|
+
func_desc = const(func_idx)
|
|
3946
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
3947
|
+
res = await self._invoke_callable_or_funcdesc(func_desc, args, is_constant=True)
|
|
3948
|
+
stack.append(res)
|
|
1468
3949
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
3950
|
+
elif op_name == "CALL_TOP":
|
|
3951
|
+
arg_count = operand
|
|
3952
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
3953
|
+
fn_obj = stack.pop() if stack else None
|
|
3954
|
+
res = await self._invoke_callable_or_funcdesc(fn_obj, args)
|
|
3955
|
+
stack.append(res)
|
|
3956
|
+
|
|
3957
|
+
# --- Arithmetic & Logic ---
|
|
3958
|
+
elif op_name == "ADD":
|
|
3959
|
+
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
3960
|
+
# Auto-unwrap evaluator objects
|
|
3961
|
+
if hasattr(a, 'value'): a = a.value
|
|
3962
|
+
if hasattr(b, 'value'): b = b.value
|
|
3963
|
+
stack.append(a + b)
|
|
3964
|
+
elif op_name == "SUB":
|
|
3965
|
+
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
3966
|
+
if hasattr(a, 'value'): a = a.value
|
|
3967
|
+
if hasattr(b, 'value'): b = b.value
|
|
3968
|
+
stack.append(a - b)
|
|
3969
|
+
elif op_name == "MUL":
|
|
3970
|
+
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
3971
|
+
if hasattr(a, 'value'): a = a.value
|
|
3972
|
+
if hasattr(b, 'value'): b = b.value
|
|
3973
|
+
stack.append(a * b)
|
|
3974
|
+
elif op_name == "DIV":
|
|
3975
|
+
b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
|
|
3976
|
+
if hasattr(a, 'value'): a = a.value
|
|
3977
|
+
if hasattr(b, 'value'): b = b.value
|
|
3978
|
+
stack.append(a / b if b != 0 else 0)
|
|
3979
|
+
elif op_name == "MOD":
|
|
3980
|
+
b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
|
|
3981
|
+
stack.append(a % b if b != 0 else 0)
|
|
3982
|
+
elif op_name == "POW":
|
|
3983
|
+
b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
|
|
3984
|
+
stack.append(a ** b)
|
|
3985
|
+
elif op_name == "NEG":
|
|
3986
|
+
a = stack.pop() if stack else 0
|
|
3987
|
+
stack.append(-a)
|
|
3988
|
+
elif op_name == "EQ":
|
|
3989
|
+
b = stack.pop() if stack else None; a = stack.pop() if stack else None
|
|
3990
|
+
stack.append(a == b)
|
|
3991
|
+
elif op_name == "NEQ":
|
|
3992
|
+
b = stack.pop() if stack else None; a = stack.pop() if stack else None
|
|
3993
|
+
stack.append(a != b)
|
|
3994
|
+
elif op_name == "LT":
|
|
3995
|
+
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
3996
|
+
stack.append(a < b)
|
|
3997
|
+
elif op_name == "GT":
|
|
3998
|
+
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
3999
|
+
stack.append(a > b)
|
|
4000
|
+
elif op_name == "LTE":
|
|
4001
|
+
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
4002
|
+
stack.append(a <= b)
|
|
4003
|
+
elif op_name == "GTE":
|
|
4004
|
+
b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
|
|
4005
|
+
stack.append(a >= b)
|
|
4006
|
+
elif op_name == "NOT":
|
|
4007
|
+
a = stack.pop() if stack else False
|
|
4008
|
+
stack.append(not a)
|
|
4009
|
+
|
|
4010
|
+
# --- Control Flow ---
|
|
4011
|
+
elif op_name == "JUMP":
|
|
4012
|
+
ip = operand
|
|
4013
|
+
elif op_name == "JUMP_IF_FALSE":
|
|
4014
|
+
cond = stack.pop() if stack else None
|
|
4015
|
+
if not cond: ip = operand
|
|
4016
|
+
elif op_name == "RETURN":
|
|
4017
|
+
return stack.pop() if stack else None
|
|
4018
|
+
|
|
4019
|
+
# --- Collections ---
|
|
4020
|
+
elif op_name == "BUILD_LIST":
|
|
4021
|
+
count = operand if operand is not None else 0
|
|
4022
|
+
elements = [None] * count
|
|
4023
|
+
for i in range(count - 1, -1, -1):
|
|
4024
|
+
elements[i] = stack.pop() if stack else None
|
|
4025
|
+
stack.append(elements)
|
|
4026
|
+
elif op_name == "BUILD_MAP":
|
|
4027
|
+
count = operand if operand is not None else 0
|
|
4028
|
+
result = {}
|
|
4029
|
+
for _ in range(count):
|
|
4030
|
+
val = stack.pop() if stack else None
|
|
4031
|
+
key = stack.pop() if stack else None
|
|
4032
|
+
result[key] = val
|
|
4033
|
+
stack.append(result)
|
|
4034
|
+
elif op_name == "BUILD_SET":
|
|
4035
|
+
count = operand if operand is not None else 0
|
|
4036
|
+
elements = [stack_pop() for _ in range(count)][::-1]
|
|
4037
|
+
stack.append(set(elements))
|
|
4038
|
+
elif op_name == "INDEX":
|
|
4039
|
+
idx = stack.pop() if stack else None
|
|
4040
|
+
obj = stack.pop() if stack else None
|
|
4041
|
+
try:
|
|
4042
|
+
if isinstance(obj, ZList):
|
|
4043
|
+
stack.append(obj.get(idx))
|
|
4044
|
+
elif isinstance(obj, ZMap):
|
|
4045
|
+
stack.append(obj.get(idx))
|
|
4046
|
+
elif isinstance(obj, ZString):
|
|
4047
|
+
stack.append(obj[idx])
|
|
1476
4048
|
else:
|
|
1477
|
-
|
|
1478
|
-
# Push back any non-task values we skipped
|
|
1479
|
-
for val in reversed(temp_stack):
|
|
4049
|
+
val = obj[idx] if obj is not None and idx is not None else None
|
|
1480
4050
|
stack.append(val)
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
4051
|
+
except (IndexError, KeyError, TypeError):
|
|
4052
|
+
stack.append(None)
|
|
4053
|
+
elif op_name == "SLICE":
|
|
4054
|
+
end = _unwrap(stack.pop() if stack else None)
|
|
4055
|
+
start = _unwrap(stack.pop() if stack else None)
|
|
4056
|
+
obj = stack.pop() if stack else None
|
|
4057
|
+
try:
|
|
4058
|
+
if isinstance(obj, ZList):
|
|
4059
|
+
stack.append(ZList(obj.elements[start:end]))
|
|
4060
|
+
elif isinstance(obj, ZString):
|
|
4061
|
+
stack.append(ZString(obj.value[start:end]))
|
|
4062
|
+
else:
|
|
4063
|
+
stack.append(obj[start:end] if obj is not None else None)
|
|
4064
|
+
except Exception:
|
|
4065
|
+
stack.append(None)
|
|
4066
|
+
elif op_name == "GET_LENGTH":
|
|
4067
|
+
obj = stack.pop() if stack else None
|
|
4068
|
+
try:
|
|
4069
|
+
if obj is None:
|
|
4070
|
+
stack.append(0)
|
|
4071
|
+
elif isinstance(obj, ZList):
|
|
4072
|
+
stack.append(len(obj.elements))
|
|
4073
|
+
elif isinstance(obj, ZMap):
|
|
4074
|
+
stack.append(len(obj.pairs))
|
|
4075
|
+
elif isinstance(obj, ZString):
|
|
4076
|
+
stack.append(len(obj.value))
|
|
4077
|
+
elif hasattr(obj, '__len__'):
|
|
4078
|
+
stack.append(len(obj))
|
|
4079
|
+
else:
|
|
4080
|
+
stack.append(0)
|
|
4081
|
+
except (TypeError, AttributeError):
|
|
4082
|
+
stack.append(0)
|
|
4083
|
+
|
|
4084
|
+
# --- Async & Events ---
|
|
4085
|
+
elif op_name == "SPAWN":
|
|
4086
|
+
# operand: tuple ("CALL", func_name, arg_count) OR index
|
|
4087
|
+
task_handle = None
|
|
4088
|
+
if isinstance(operand, tuple) and operand[0] == "CALL":
|
|
4089
|
+
fn_name = operand[1]; arg_count = operand[2]
|
|
4090
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1]
|
|
4091
|
+
fn = self.builtins.get(fn_name) or self.env.get(fn_name)
|
|
4092
|
+
coro = self._to_coro(fn, args)
|
|
4093
|
+
|
|
1484
4094
|
# Use async optimizer if available
|
|
1485
4095
|
if self.async_optimizer:
|
|
1486
|
-
|
|
4096
|
+
coro = self.async_optimizer.spawn(coro)
|
|
4097
|
+
task = asyncio.create_task(coro)
|
|
4098
|
+
else:
|
|
4099
|
+
task = asyncio.create_task(coro)
|
|
4100
|
+
|
|
4101
|
+
self._task_counter += 1
|
|
4102
|
+
tid = f"task_{self._task_counter}"
|
|
4103
|
+
self._tasks[tid] = task
|
|
4104
|
+
task_handle = tid
|
|
4105
|
+
stack.append(task_handle)
|
|
4106
|
+
|
|
4107
|
+
elif op_name == "SPAWN_CALL":
|
|
4108
|
+
task_handle = None
|
|
4109
|
+
if isinstance(operand, (list, tuple)) and operand:
|
|
4110
|
+
name_idx = operand[0]
|
|
4111
|
+
arg_count = operand[1] if len(operand) > 1 else 0
|
|
4112
|
+
fn_name = const(name_idx)
|
|
4113
|
+
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
4114
|
+
fn = self.builtins.get(fn_name) or self.env.get(fn_name)
|
|
4115
|
+
coro = self._to_coro(fn, args)
|
|
4116
|
+
if self.async_optimizer:
|
|
4117
|
+
coro = self.async_optimizer.spawn(coro)
|
|
4118
|
+
task = asyncio.create_task(coro)
|
|
4119
|
+
self._task_counter += 1
|
|
4120
|
+
tid = f"task_{self._task_counter}"
|
|
4121
|
+
self._tasks[tid] = task
|
|
4122
|
+
task_handle = tid
|
|
4123
|
+
stack.append(task_handle)
|
|
4124
|
+
|
|
4125
|
+
elif op_name == "AWAIT":
|
|
4126
|
+
# Keep popping until we find a task to await
|
|
4127
|
+
result_found = False
|
|
4128
|
+
temp_stack = self.allocate_list(0)
|
|
4129
|
+
|
|
4130
|
+
while stack and not result_found:
|
|
4131
|
+
top = stack.pop()
|
|
4132
|
+
|
|
4133
|
+
if isinstance(top, str) and top in self._tasks:
|
|
4134
|
+
# Use async optimizer if available
|
|
4135
|
+
if self.async_optimizer:
|
|
4136
|
+
res = await self.async_optimizer.await_optimized(self._tasks[top])
|
|
4137
|
+
else:
|
|
4138
|
+
res = await self._tasks[top]
|
|
4139
|
+
# Push back any non-task values we skipped
|
|
4140
|
+
for val in reversed(temp_stack):
|
|
4141
|
+
stack.append(val)
|
|
4142
|
+
stack.append(res)
|
|
4143
|
+
result_found = True
|
|
4144
|
+
elif asyncio.iscoroutine(top) or isinstance(top, asyncio.Future):
|
|
4145
|
+
# Use async optimizer if available
|
|
4146
|
+
if self.async_optimizer:
|
|
4147
|
+
res = await self.async_optimizer.await_optimized(top)
|
|
4148
|
+
else:
|
|
4149
|
+
res = await top
|
|
4150
|
+
# Push back any non-task values we skipped
|
|
4151
|
+
for val in reversed(temp_stack):
|
|
4152
|
+
stack.append(val)
|
|
4153
|
+
stack.append(res)
|
|
4154
|
+
result_found = True
|
|
1487
4155
|
else:
|
|
1488
|
-
|
|
1489
|
-
|
|
4156
|
+
# Not a task, save it and keep looking
|
|
4157
|
+
temp_stack.append(top)
|
|
4158
|
+
|
|
4159
|
+
# If no task was found, put everything back
|
|
4160
|
+
if not result_found:
|
|
1490
4161
|
for val in reversed(temp_stack):
|
|
1491
4162
|
stack.append(val)
|
|
1492
|
-
|
|
1493
|
-
|
|
4163
|
+
|
|
4164
|
+
if temp_stack:
|
|
4165
|
+
temp_stack.clear()
|
|
4166
|
+
self.release_list(temp_stack)
|
|
4167
|
+
|
|
4168
|
+
elif op_name == "REGISTER_EVENT":
|
|
4169
|
+
event_parts = operand if isinstance(operand, (list, tuple)) else (operand,)
|
|
4170
|
+
event_name = const(event_parts[0]) if event_parts else None
|
|
4171
|
+
handler = const(event_parts[1]) if len(event_parts) > 1 else None
|
|
4172
|
+
if event_name is not None:
|
|
4173
|
+
handlers = self._events.setdefault(event_name, [])
|
|
4174
|
+
if handler and handler not in handlers:
|
|
4175
|
+
handlers.append(handler)
|
|
4176
|
+
|
|
4177
|
+
elif op_name == "EMIT_EVENT":
|
|
4178
|
+
event_ref = const(operand[0]) if operand else None
|
|
4179
|
+
payload_ref = None
|
|
4180
|
+
event_name = event_ref
|
|
4181
|
+
if isinstance(event_ref, (list, tuple)) and event_ref:
|
|
4182
|
+
event_name = event_ref[0]
|
|
4183
|
+
if len(event_ref) > 1:
|
|
4184
|
+
payload_ref = event_ref[1]
|
|
4185
|
+
|
|
4186
|
+
payload = None
|
|
4187
|
+
if stack:
|
|
4188
|
+
payload = stack.pop()
|
|
4189
|
+
elif payload_ref is not None:
|
|
4190
|
+
payload = const(payload_ref)
|
|
4191
|
+
elif isinstance(operand, (list, tuple)) and len(operand) > 1:
|
|
4192
|
+
payload = const(operand[1])
|
|
4193
|
+
|
|
4194
|
+
payload = _unwrap(payload)
|
|
4195
|
+
|
|
4196
|
+
handlers = self._events.get(event_name, [])
|
|
4197
|
+
for h in handlers:
|
|
4198
|
+
fn = self.builtins.get(h) or self.env.get(h)
|
|
4199
|
+
if fn is None:
|
|
4200
|
+
continue
|
|
4201
|
+
await self._call_builtin_async_obj(fn, [payload], wrap_args=False)
|
|
4202
|
+
|
|
4203
|
+
elif op_name == "IMPORT":
|
|
4204
|
+
mod_name = const(operand[0])
|
|
4205
|
+
alias = const(operand[1]) if isinstance(operand, (list,tuple)) and len(operand) > 1 else ""
|
|
4206
|
+
names = const(operand[2]) if isinstance(operand, (list, tuple)) and len(operand) > 2 else []
|
|
4207
|
+
is_named = const(operand[3]) if isinstance(operand, (list, tuple)) and len(operand) > 3 else False
|
|
4208
|
+
self._execute_import(mod_name, alias=alias or "", names=names, is_named=bool(is_named))
|
|
4209
|
+
|
|
4210
|
+
elif op_name == "EXPORT":
|
|
4211
|
+
name = None
|
|
4212
|
+
value = None
|
|
4213
|
+
if isinstance(operand, (list, tuple)) and operand:
|
|
4214
|
+
name = const(operand[0])
|
|
4215
|
+
if len(operand) > 1:
|
|
4216
|
+
value = const(operand[1])
|
|
4217
|
+
if name is None:
|
|
4218
|
+
name = stack.pop() if stack else None
|
|
4219
|
+
if value is None:
|
|
4220
|
+
value = stack.pop() if stack else None
|
|
4221
|
+
|
|
4222
|
+
export_fn = getattr(self.env, "export", None)
|
|
4223
|
+
if callable(export_fn):
|
|
4224
|
+
try:
|
|
4225
|
+
export_fn(name, value)
|
|
4226
|
+
except Exception:
|
|
4227
|
+
pass
|
|
1494
4228
|
else:
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
# If no task was found, put everything back
|
|
1499
|
-
if not result_found:
|
|
1500
|
-
for val in reversed(temp_stack):
|
|
1501
|
-
stack.append(val)
|
|
1502
|
-
|
|
1503
|
-
if temp_stack:
|
|
1504
|
-
temp_stack.clear()
|
|
1505
|
-
self.release_list(temp_stack)
|
|
1506
|
-
|
|
1507
|
-
elif op_name == "REGISTER_EVENT":
|
|
1508
|
-
event_parts = operand if isinstance(operand, (list, tuple)) else (operand,)
|
|
1509
|
-
event_name = const(event_parts[0]) if event_parts else None
|
|
1510
|
-
handler = const(event_parts[1]) if len(event_parts) > 1 else None
|
|
1511
|
-
if event_name is not None:
|
|
1512
|
-
handlers = self._events.setdefault(event_name, [])
|
|
1513
|
-
if handler and handler not in handlers:
|
|
1514
|
-
handlers.append(handler)
|
|
1515
|
-
|
|
1516
|
-
elif op_name == "EMIT_EVENT":
|
|
1517
|
-
event_ref = const(operand[0]) if operand else None
|
|
1518
|
-
payload_ref = None
|
|
1519
|
-
event_name = event_ref
|
|
1520
|
-
if isinstance(event_ref, (list, tuple)) and event_ref:
|
|
1521
|
-
event_name = event_ref[0]
|
|
1522
|
-
if len(event_ref) > 1:
|
|
1523
|
-
payload_ref = event_ref[1]
|
|
1524
|
-
|
|
1525
|
-
payload = None
|
|
1526
|
-
if stack:
|
|
1527
|
-
payload = stack.pop()
|
|
1528
|
-
elif payload_ref is not None:
|
|
1529
|
-
payload = const(payload_ref)
|
|
1530
|
-
elif isinstance(operand, (list, tuple)) and len(operand) > 1:
|
|
1531
|
-
payload = const(operand[1])
|
|
1532
|
-
|
|
1533
|
-
payload = _unwrap(payload)
|
|
1534
|
-
|
|
1535
|
-
handlers = self._events.get(event_name, [])
|
|
1536
|
-
for h in handlers:
|
|
1537
|
-
fn = self.builtins.get(h) or self.env.get(h)
|
|
1538
|
-
if fn is None:
|
|
1539
|
-
continue
|
|
1540
|
-
await self._call_builtin_async_obj(fn, [payload], wrap_args=False)
|
|
4229
|
+
self.env[name] = value
|
|
4230
|
+
self._bump_env_version(name, value)
|
|
1541
4231
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
elif op_name == "ASSERT_PROTOCOL":
|
|
1558
|
-
obj_name = const(operand[0])
|
|
1559
|
-
spec = const(operand[1])
|
|
1560
|
-
obj = self.env.get(obj_name)
|
|
1561
|
-
ok = True
|
|
1562
|
-
missing = []
|
|
1563
|
-
for m in spec.get("methods", []):
|
|
1564
|
-
if not hasattr(obj, m):
|
|
1565
|
-
ok = False; missing.append(m)
|
|
1566
|
-
stack.append((ok, missing))
|
|
1567
|
-
|
|
1568
|
-
# --- Blockchain Specific Opcodes ---
|
|
1569
|
-
|
|
1570
|
-
elif op_name == "HASH_BLOCK":
|
|
1571
|
-
block_data = stack.pop() if stack else ""
|
|
1572
|
-
if isinstance(block_data, dict):
|
|
1573
|
-
import json; block_data = json.dumps(block_data, sort_keys=True)
|
|
1574
|
-
if not isinstance(block_data, (bytes, str)): block_data = str(block_data)
|
|
1575
|
-
if isinstance(block_data, str): block_data = block_data.encode('utf-8')
|
|
1576
|
-
stack.append(hashlib.sha256(block_data).hexdigest())
|
|
4232
|
+
elif op_name == "WRITE":
|
|
4233
|
+
payload = stack.pop() if stack else None
|
|
4234
|
+
path = stack.pop() if stack else None
|
|
4235
|
+
try:
|
|
4236
|
+
if path is not None:
|
|
4237
|
+
with open(path, "w") as f:
|
|
4238
|
+
if isinstance(payload, bytes):
|
|
4239
|
+
f.write(payload.decode("utf-8"))
|
|
4240
|
+
else:
|
|
4241
|
+
f.write(str(payload) if payload is not None else "")
|
|
4242
|
+
stack.append(True)
|
|
4243
|
+
else:
|
|
4244
|
+
stack.append(False)
|
|
4245
|
+
except Exception:
|
|
4246
|
+
stack.append(False)
|
|
1577
4247
|
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
if verify_fn:
|
|
1583
|
-
res = await self._invoke_callable_or_funcdesc(verify_fn, [sig, msg, pk])
|
|
1584
|
-
stack.append(res)
|
|
4248
|
+
elif op_name == "DEFINE_SCREEN":
|
|
4249
|
+
if isinstance(operand, (list, tuple)) and len(operand) >= 2:
|
|
4250
|
+
name = const(operand[0])
|
|
4251
|
+
props = const(operand[1])
|
|
1585
4252
|
else:
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
leaf_count = operand if operand is not None else 0
|
|
1594
|
-
if leaf_count <= 0:
|
|
1595
|
-
stack.append("")
|
|
1596
|
-
else:
|
|
1597
|
-
leaves = [stack.pop() for _ in range(leaf_count)][::-1] if len(stack) >= leaf_count else []
|
|
1598
|
-
hashes = []
|
|
1599
|
-
for leaf in leaves:
|
|
1600
|
-
if isinstance(leaf, dict):
|
|
1601
|
-
import json; leaf = json.dumps(leaf, sort_keys=True)
|
|
1602
|
-
if not isinstance(leaf, (str, bytes)): leaf = str(leaf)
|
|
1603
|
-
if isinstance(leaf, str): leaf = leaf.encode('utf-8')
|
|
1604
|
-
hashes.append(hashlib.sha256(leaf).hexdigest())
|
|
1605
|
-
|
|
1606
|
-
while len(hashes) > 1:
|
|
1607
|
-
if len(hashes) % 2 != 0: hashes.append(hashes[-1])
|
|
1608
|
-
new_hashes = []
|
|
1609
|
-
for i in range(0, len(hashes), 2):
|
|
1610
|
-
combined = (hashes[i] + hashes[i+1]).encode('utf-8')
|
|
1611
|
-
new_hashes.append(hashlib.sha256(combined).hexdigest())
|
|
1612
|
-
hashes = new_hashes
|
|
1613
|
-
stack.append(hashes[0] if hashes else "")
|
|
1614
|
-
|
|
1615
|
-
elif op_name == "STATE_READ":
|
|
1616
|
-
if operand is None:
|
|
1617
|
-
key = _unwrap(stack.pop() if stack else None)
|
|
1618
|
-
else:
|
|
1619
|
-
key = const(operand)
|
|
1620
|
-
stack.append(self.env.setdefault("_blockchain_state", {}).get(key))
|
|
1621
|
-
|
|
1622
|
-
elif op_name == "STATE_WRITE":
|
|
1623
|
-
val = _unwrap(stack.pop() if stack else None)
|
|
1624
|
-
if operand is None:
|
|
1625
|
-
key = _unwrap(stack.pop() if stack else None)
|
|
1626
|
-
else:
|
|
1627
|
-
key = const(operand)
|
|
1628
|
-
if self.env.get("_in_transaction", False):
|
|
1629
|
-
self.env.setdefault("_tx_pending_state", {})[key] = val
|
|
1630
|
-
else:
|
|
1631
|
-
self.env.setdefault("_blockchain_state", {})[key] = val
|
|
1632
|
-
|
|
1633
|
-
elif op_name == "TX_BEGIN":
|
|
1634
|
-
self.env["_in_transaction"] = True
|
|
1635
|
-
self.env["_tx_pending_state"] = {}
|
|
1636
|
-
self.env["_tx_snapshot"] = dict(self.env.get("_blockchain_state", {}))
|
|
1637
|
-
if self.use_memory_manager: self.env["_tx_memory_snapshot"] = dict(self._managed_objects)
|
|
4253
|
+
props = stack.pop() if stack else None
|
|
4254
|
+
name = stack.pop() if stack else None
|
|
4255
|
+
if _BACKEND_AVAILABLE:
|
|
4256
|
+
_BACKEND.define_screen(name, props)
|
|
4257
|
+
else:
|
|
4258
|
+
key = _unwrap(name)
|
|
4259
|
+
self.env.setdefault("screens", {})[key] = props
|
|
1638
4260
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
4261
|
+
elif op_name == "DEFINE_COMPONENT":
|
|
4262
|
+
if isinstance(operand, (list, tuple)) and len(operand) >= 2:
|
|
4263
|
+
name = const(operand[0])
|
|
4264
|
+
props = const(operand[1])
|
|
4265
|
+
else:
|
|
4266
|
+
props = stack.pop() if stack else None
|
|
4267
|
+
name = stack.pop() if stack else None
|
|
4268
|
+
if _BACKEND_AVAILABLE:
|
|
4269
|
+
_BACKEND.define_component(name, props)
|
|
4270
|
+
else:
|
|
4271
|
+
key = _unwrap(name)
|
|
4272
|
+
self.env.setdefault("components", {})[key] = props
|
|
1645
4273
|
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
4274
|
+
elif op_name == "DEFINE_THEME":
|
|
4275
|
+
if isinstance(operand, (list, tuple)) and len(operand) >= 2:
|
|
4276
|
+
name = const(operand[0])
|
|
4277
|
+
props = const(operand[1])
|
|
4278
|
+
else:
|
|
4279
|
+
props = stack.pop() if stack else None
|
|
4280
|
+
name = stack.pop() if stack else None
|
|
4281
|
+
key = _unwrap(name)
|
|
4282
|
+
self.env.setdefault("themes", {})[key] = props
|
|
4283
|
+
|
|
4284
|
+
elif op_name == "DEFINE_ENUM":
|
|
4285
|
+
enum_name = _unwrap(const(operand[0]))
|
|
4286
|
+
enum_map = const(operand[1])
|
|
4287
|
+
self.env.setdefault("enums", {})[enum_name] = enum_map
|
|
4288
|
+
self.env[enum_name] = enum_map
|
|
4289
|
+
self._bump_env_version(enum_name, enum_map)
|
|
4290
|
+
|
|
4291
|
+
elif op_name == "DEFINE_PROTOCOL":
|
|
4292
|
+
proto_name = _unwrap(const(operand[0]))
|
|
4293
|
+
proto_spec = const(operand[1])
|
|
4294
|
+
self.env.setdefault("protocols", {})[proto_name] = proto_spec
|
|
4295
|
+
self.env[proto_name] = proto_spec
|
|
4296
|
+
self._bump_env_version(proto_name, proto_spec)
|
|
4297
|
+
|
|
4298
|
+
elif op_name == "ASSERT_PROTOCOL":
|
|
4299
|
+
obj_name = const(operand[0])
|
|
4300
|
+
spec = const(operand[1])
|
|
4301
|
+
obj = self.env.get(obj_name)
|
|
4302
|
+
ok = True
|
|
4303
|
+
missing = []
|
|
4304
|
+
for m in spec.get("methods", []):
|
|
4305
|
+
if not hasattr(obj, m):
|
|
4306
|
+
ok = False; missing.append(m)
|
|
4307
|
+
stack.append((ok, missing))
|
|
4308
|
+
|
|
4309
|
+
# --- Blockchain Specific Opcodes ---
|
|
4310
|
+
|
|
4311
|
+
elif op_name == "HASH_BLOCK":
|
|
4312
|
+
block_data = stack.pop() if stack else ""
|
|
4313
|
+
if isinstance(block_data, dict):
|
|
4314
|
+
import json; block_data = json.dumps(block_data, sort_keys=True)
|
|
4315
|
+
if not isinstance(block_data, (bytes, str)): block_data = str(block_data)
|
|
4316
|
+
if isinstance(block_data, str): block_data = block_data.encode('utf-8')
|
|
4317
|
+
try:
|
|
4318
|
+
from Crypto.Hash import keccak as _keccak_mod
|
|
4319
|
+
h = _keccak_mod.new(digest_bits=256, data=block_data)
|
|
4320
|
+
stack.append(h.hexdigest())
|
|
4321
|
+
except ImportError:
|
|
4322
|
+
stack.append(hashlib.sha256(block_data).hexdigest())
|
|
4323
|
+
|
|
4324
|
+
elif op_name == "VERIFY_SIGNATURE":
|
|
4325
|
+
if len(stack) >= 3:
|
|
4326
|
+
pk = stack.pop(); msg = stack.pop(); sig = stack.pop()
|
|
4327
|
+
verify_fn = self.builtins.get("verify_sig") or self.env.get("verify_sig")
|
|
4328
|
+
if verify_fn:
|
|
4329
|
+
res = await self._invoke_callable_or_funcdesc(verify_fn, [sig, msg, pk])
|
|
4330
|
+
stack.append(res)
|
|
4331
|
+
else:
|
|
4332
|
+
# Use real CryptoPlugin verification when available.
|
|
4333
|
+
# No SHA-256 fallback — forging a SHA-256 hash is trivial.
|
|
4334
|
+
try:
|
|
4335
|
+
from ..blockchain.crypto import CryptoPlugin
|
|
4336
|
+
sig_str = sig.value if hasattr(sig, 'value') else str(sig)
|
|
4337
|
+
msg_str = msg.value if hasattr(msg, 'value') else str(msg)
|
|
4338
|
+
pk_str = pk.value if hasattr(pk, 'value') else str(pk)
|
|
4339
|
+
stack.append(CryptoPlugin.verify_signature(msg_str, sig_str, pk_str))
|
|
4340
|
+
except ImportError:
|
|
4341
|
+
# CryptoPlugin unavailable — always reject
|
|
4342
|
+
stack.append(False)
|
|
4343
|
+
else:
|
|
4344
|
+
stack.append(False)
|
|
4345
|
+
|
|
4346
|
+
elif op_name == "MERKLE_ROOT":
|
|
4347
|
+
leaf_count = operand if operand is not None else 0
|
|
4348
|
+
if leaf_count <= 0 or len(stack) < leaf_count:
|
|
4349
|
+
stack.append("")
|
|
4350
|
+
else:
|
|
4351
|
+
leaves = [stack.pop() for _ in range(leaf_count)][::-1] if len(stack) >= leaf_count else []
|
|
4352
|
+
hashes = []
|
|
4353
|
+
for leaf in leaves:
|
|
4354
|
+
if isinstance(leaf, dict):
|
|
4355
|
+
import json; leaf = json.dumps(leaf, sort_keys=True)
|
|
4356
|
+
if not isinstance(leaf, (str, bytes)): leaf = str(leaf)
|
|
4357
|
+
if isinstance(leaf, str): leaf = leaf.encode('utf-8')
|
|
4358
|
+
hashes.append(hashlib.sha256(leaf).hexdigest())
|
|
4359
|
+
|
|
4360
|
+
while len(hashes) > 1:
|
|
4361
|
+
if len(hashes) % 2 != 0: hashes.append(hashes[-1])
|
|
4362
|
+
new_hashes = []
|
|
4363
|
+
for i in range(0, len(hashes), 2):
|
|
4364
|
+
combined = (hashes[i] + hashes[i+1]).encode('utf-8')
|
|
4365
|
+
new_hashes.append(hashlib.sha256(combined).hexdigest())
|
|
4366
|
+
hashes = new_hashes
|
|
4367
|
+
stack.append(hashes[0] if hashes else "")
|
|
4368
|
+
|
|
4369
|
+
elif op_name == "STATE_READ":
|
|
4370
|
+
if operand is None:
|
|
4371
|
+
key = _unwrap(stack.pop() if stack else None)
|
|
4372
|
+
else:
|
|
4373
|
+
key = const(operand)
|
|
4374
|
+
stack.append(self.env.setdefault("_blockchain_state", {}).get(key))
|
|
4375
|
+
|
|
4376
|
+
elif op_name == "STATE_WRITE":
|
|
4377
|
+
val = _unwrap(stack.pop() if stack else None)
|
|
4378
|
+
if operand is None:
|
|
4379
|
+
key = _unwrap(stack.pop() if stack else None)
|
|
4380
|
+
else:
|
|
4381
|
+
key = const(operand)
|
|
4382
|
+
if self.env.get("_in_transaction", False):
|
|
4383
|
+
self.env.setdefault("_tx_pending_state", {})[key] = val
|
|
4384
|
+
else:
|
|
4385
|
+
self.env.setdefault("_blockchain_state", {})[key] = val
|
|
4386
|
+
|
|
4387
|
+
elif op_name == "TX_BEGIN":
|
|
4388
|
+
# Support nested transactions via a stack
|
|
4389
|
+
tx_stack = self.env.setdefault("_tx_stack", [])
|
|
4390
|
+
tx_stack.append({
|
|
4391
|
+
"snapshot": dict(self.env.get("_blockchain_state", {})),
|
|
4392
|
+
"pending": dict(self.env.get("_tx_pending_state", {})),
|
|
4393
|
+
})
|
|
4394
|
+
self.env["_in_transaction"] = True
|
|
1650
4395
|
self.env["_tx_pending_state"] = {}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
if
|
|
1660
|
-
|
|
4396
|
+
self.env["_tx_snapshot"] = dict(self.env.get("_blockchain_state", {}))
|
|
4397
|
+
if self.use_memory_manager: self.env["_tx_memory_snapshot"] = dict(self._managed_objects)
|
|
4398
|
+
|
|
4399
|
+
elif op_name == "SETUP_TRY":
|
|
4400
|
+
handler = int(operand) if operand is not None else ip
|
|
4401
|
+
try_stack.append(handler)
|
|
4402
|
+
|
|
4403
|
+
elif op_name == "POP_TRY":
|
|
4404
|
+
if try_stack:
|
|
4405
|
+
try_stack.pop()
|
|
4406
|
+
|
|
4407
|
+
elif op_name == "THROW":
|
|
4408
|
+
exc = stack.pop() if stack else None
|
|
4409
|
+
if try_stack:
|
|
4410
|
+
handler = try_stack.pop()
|
|
4411
|
+
stack.append(exc)
|
|
4412
|
+
ip = handler
|
|
4413
|
+
else:
|
|
4414
|
+
msg = exc.value if hasattr(exc, "value") else exc
|
|
4415
|
+
raise ZEvaluationError(str(msg))
|
|
4416
|
+
|
|
4417
|
+
elif op_name == "TX_COMMIT":
|
|
4418
|
+
if self.env.get("_in_transaction", False):
|
|
4419
|
+
self.env.setdefault("_blockchain_state", {}).update(self.env.get("_tx_pending_state", {}))
|
|
4420
|
+
self.env["_tx_pending_state"] = {}
|
|
4421
|
+
self.env.pop("_tx_snapshot", None)
|
|
4422
|
+
if "_tx_memory_snapshot" in self.env: del self.env["_tx_memory_snapshot"]
|
|
4423
|
+
# Restore outer TX context if nested
|
|
4424
|
+
tx_stack = self.env.get("_tx_stack", [])
|
|
4425
|
+
if tx_stack:
|
|
4426
|
+
tx_stack.pop()
|
|
4427
|
+
self.env["_in_transaction"] = bool(tx_stack)
|
|
4428
|
+
|
|
4429
|
+
elif op_name == "TX_REVERT":
|
|
4430
|
+
if self.env.get("_in_transaction", False):
|
|
4431
|
+
self.env["_blockchain_state"] = dict(self.env.get("_tx_snapshot", {}))
|
|
4432
|
+
self.env["_tx_pending_state"] = {}
|
|
4433
|
+
self.env.pop("_tx_snapshot", None)
|
|
4434
|
+
if self.use_memory_manager and "_tx_memory_snapshot" in self.env:
|
|
4435
|
+
self._managed_objects = dict(self.env["_tx_memory_snapshot"])
|
|
4436
|
+
del self.env["_tx_memory_snapshot"]
|
|
4437
|
+
# Restore outer TX context if nested
|
|
4438
|
+
tx_stack = self.env.get("_tx_stack", [])
|
|
4439
|
+
if tx_stack:
|
|
4440
|
+
outer = tx_stack.pop()
|
|
4441
|
+
if tx_stack:
|
|
4442
|
+
self.env["_tx_snapshot"] = outer["snapshot"]
|
|
4443
|
+
self.env["_tx_pending_state"] = outer["pending"]
|
|
4444
|
+
self.env["_in_transaction"] = bool(self.env.get("_tx_stack", []))
|
|
4445
|
+
|
|
4446
|
+
elif op_name == "ENABLE_ERROR_MODE":
|
|
4447
|
+
self.env["_continue_on_error"] = True
|
|
4448
|
+
if self.debug: print("[VM] Error Recovery Mode ENABLED")
|
|
4449
|
+
|
|
4450
|
+
elif op_name == "GAS_CHARGE":
|
|
4451
|
+
amount = operand if operand is not None else 0
|
|
4452
|
+
# Delegate to the unified GasMetering system when available
|
|
4453
|
+
if self.gas_metering is not None:
|
|
4454
|
+
if not self.gas_metering.consume("GAS_CHARGE", amount=amount):
|
|
4455
|
+
if self.env.get("_in_transaction", False):
|
|
4456
|
+
self.env["_blockchain_state"] = dict(self.env.get("_tx_snapshot", {}))
|
|
4457
|
+
self.env["_in_transaction"] = False
|
|
4458
|
+
self.env.pop("_tx_snapshot", None)
|
|
4459
|
+
raise OutOfGasError(
|
|
4460
|
+
self.gas_metering.gas_used,
|
|
4461
|
+
self.gas_metering.gas_limit,
|
|
4462
|
+
"GAS_CHARGE"
|
|
4463
|
+
)
|
|
4464
|
+
# Sync env-based counter for backward compat
|
|
4465
|
+
if "_gas_remaining" in self.env:
|
|
4466
|
+
self.env["_gas_remaining"] = max(0, self.env["_gas_remaining"] - amount)
|
|
4467
|
+
else:
|
|
4468
|
+
# Fallback to env-based tracking when no GasMetering
|
|
4469
|
+
current = self.env.get("_gas_remaining", float('inf'))
|
|
4470
|
+
if current != float('inf'):
|
|
4471
|
+
new_gas = current - amount
|
|
4472
|
+
if new_gas < 0:
|
|
4473
|
+
if self.env.get("_in_transaction", False):
|
|
4474
|
+
self.env["_blockchain_state"] = dict(self.env.get("_tx_snapshot", {}))
|
|
4475
|
+
self.env["_in_transaction"] = False
|
|
4476
|
+
raise ZEvaluationError(
|
|
4477
|
+
f"Out of gas: required {amount}, remaining {current}")
|
|
4478
|
+
self.env["_gas_remaining"] = new_gas
|
|
4479
|
+
|
|
4480
|
+
elif op_name == "REQUIRE":
|
|
4481
|
+
message = stack.pop() if stack else "Requirement failed"
|
|
4482
|
+
if hasattr(message, 'value'): message = message.value
|
|
4483
|
+
condition = stack.pop() if stack else False
|
|
4484
|
+
cond_val = condition.value if hasattr(condition, 'value') else condition
|
|
4485
|
+
|
|
4486
|
+
if not cond_val:
|
|
1661
4487
|
if self.env.get("_in_transaction", False):
|
|
1662
4488
|
self.env["_blockchain_state"] = dict(self.env.get("_tx_snapshot", {}))
|
|
1663
4489
|
self.env["_in_transaction"] = False
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
4490
|
+
self.env["_tx_pending_state"] = {}
|
|
4491
|
+
self.env.pop("_tx_snapshot", None)
|
|
4492
|
+
self.env.pop("_tx_memory_snapshot", None)
|
|
4493
|
+
# Clean up TX stack
|
|
4494
|
+
tx_stack = self.env.get("_tx_stack", [])
|
|
4495
|
+
if tx_stack: tx_stack.pop()
|
|
4496
|
+
self.env["_in_transaction"] = bool(tx_stack)
|
|
4497
|
+
raise ZEvaluationError(f"Requirement failed: {message}")
|
|
4498
|
+
|
|
4499
|
+
elif op_name == "DEFINE_CONTRACT":
|
|
4500
|
+
contract_obj = self._build_smart_contract(
|
|
4501
|
+
operand, stack, lambda: stack.pop() if stack else None,
|
|
4502
|
+
lambda idx: consts[idx] if isinstance(idx, int) and 0 <= idx < len(consts) else idx,
|
|
4503
|
+
self.env
|
|
4504
|
+
)
|
|
4505
|
+
stack.append(contract_obj)
|
|
4506
|
+
|
|
4507
|
+
elif op_name == "DEFINE_ENTITY":
|
|
4508
|
+
member_count = operand
|
|
4509
|
+
members = {}
|
|
4510
|
+
for _ in range(member_count):
|
|
4511
|
+
key_obj = stack.pop() if stack else None
|
|
4512
|
+
val_obj = stack.pop() if stack else None
|
|
4513
|
+
key_str = key_obj.value if hasattr(key_obj, 'value') else str(key_obj)
|
|
4514
|
+
members[key_str] = val_obj
|
|
4515
|
+
|
|
4516
|
+
name_obj = stack.pop() if stack else None
|
|
4517
|
+
# Create Entity (using Map for now, can be specialized Entity class later)
|
|
4518
|
+
members['_type'] = 'entity'
|
|
4519
|
+
members['_name'] = name_obj.value if hasattr(name_obj, 'value') else str(name_obj)
|
|
4520
|
+
stack.append(ZMap(members))
|
|
4521
|
+
|
|
4522
|
+
elif op_name == "DEFINE_CAPABILITY":
|
|
4523
|
+
name = stack.pop() if stack else None
|
|
4524
|
+
definition = stack.pop() if stack else {}
|
|
4525
|
+
if hasattr(name, 'value'): name = name.value
|
|
4526
|
+
self.env.setdefault("_capabilities", {})[name] = definition
|
|
4527
|
+
|
|
4528
|
+
elif op_name == "GRANT_CAPABILITY":
|
|
4529
|
+
count = operand
|
|
4530
|
+
caps = [stack.pop() for _ in range(count)][::-1]
|
|
4531
|
+
entity_name = stack.pop() if stack else None
|
|
4532
|
+
if hasattr(entity_name, 'value'): entity_name = entity_name.value
|
|
4533
|
+
|
|
4534
|
+
grants = self.env.setdefault("_grants", {})
|
|
4535
|
+
entity_grants = grants.setdefault(entity_name, set())
|
|
4536
|
+
|
|
4537
|
+
for cap in caps:
|
|
4538
|
+
c_val = cap.value if hasattr(cap, 'value') else str(cap)
|
|
4539
|
+
entity_grants.add(c_val)
|
|
4540
|
+
|
|
4541
|
+
elif op_name == "REVOKE_CAPABILITY":
|
|
4542
|
+
count = operand
|
|
4543
|
+
caps = [stack.pop() for _ in range(count)][::-1]
|
|
4544
|
+
entity_name = stack.pop() if stack else None
|
|
4545
|
+
if hasattr(entity_name, 'value'): entity_name = entity_name.value
|
|
4546
|
+
|
|
4547
|
+
if "_grants" in self.env and entity_name in self.env["_grants"]:
|
|
4548
|
+
entity_grants = self.env["_grants"][entity_name]
|
|
4549
|
+
for cap in caps:
|
|
4550
|
+
c_val = cap.value if hasattr(cap, 'value') else str(cap)
|
|
4551
|
+
if c_val in entity_grants:
|
|
4552
|
+
entity_grants.remove(c_val)
|
|
4553
|
+
|
|
4554
|
+
elif op_name == "AUDIT_LOG":
|
|
4555
|
+
ts = stack_pop()
|
|
4556
|
+
action = stack_pop()
|
|
4557
|
+
data = stack_pop()
|
|
4558
|
+
# Unwrap
|
|
4559
|
+
ts = ts.value if hasattr(ts, 'value') else ts
|
|
4560
|
+
action = action.value if hasattr(action, 'value') else action
|
|
4561
|
+
data = data.value if hasattr(data, 'value') else data
|
|
4562
|
+
|
|
4563
|
+
entry = {"timestamp": ts, "action": action, "data": data}
|
|
4564
|
+
self.env.setdefault("_audit_log", []).append(entry)
|
|
4565
|
+
if self.debug: print(f"[AUDIT] {entry}")
|
|
4566
|
+
|
|
4567
|
+
elif op_name == "RESTRICT_ACCESS":
|
|
4568
|
+
restriction = stack_pop()
|
|
4569
|
+
prop = stack_pop()
|
|
4570
|
+
obj = stack_pop()
|
|
4571
|
+
|
|
4572
|
+
# Enforce access restrictions for smart contract actions.
|
|
4573
|
+
# ``restriction`` can be:
|
|
4574
|
+
# - a string like "owner_only" — compared against TX.caller
|
|
4575
|
+
# - a list of allowed addresses
|
|
4576
|
+
# - a callable predicate
|
|
4577
|
+
r_key = f"{obj}.{prop}" if prop else str(obj)
|
|
4578
|
+
self.env.setdefault("_restrictions", {})[r_key] = restriction
|
|
4579
|
+
|
|
4580
|
+
# Real enforcement: check if the current caller matches
|
|
4581
|
+
caller = None
|
|
4582
|
+
tx_obj = self.env.get("TX")
|
|
4583
|
+
if tx_obj is not None:
|
|
4584
|
+
if hasattr(tx_obj, 'get'):
|
|
4585
|
+
caller_val = tx_obj.get(ZString("caller")) if hasattr(tx_obj, 'get') else None
|
|
4586
|
+
if caller_val is not None:
|
|
4587
|
+
caller = caller_val.value if hasattr(caller_val, 'value') else str(caller_val)
|
|
4588
|
+
|
|
4589
|
+
restriction_val = restriction.value if hasattr(restriction, 'value') else restriction
|
|
4590
|
+
if isinstance(restriction_val, str) and restriction_val == "owner_only":
|
|
4591
|
+
owner = self.env.get("owner")
|
|
4592
|
+
if owner is not None:
|
|
4593
|
+
owner_val = owner.value if hasattr(owner, 'value') else str(owner)
|
|
4594
|
+
if caller and caller != owner_val:
|
|
4595
|
+
raise ZEvaluationError(
|
|
4596
|
+
f"Access denied: '{r_key}' restricted to owner only"
|
|
4597
|
+
)
|
|
4598
|
+
elif isinstance(restriction_val, (list, tuple)):
|
|
4599
|
+
allowed = [a.value if hasattr(a, 'value') else str(a) for a in restriction_val]
|
|
4600
|
+
if caller and caller not in allowed:
|
|
4601
|
+
raise ZEvaluationError(
|
|
4602
|
+
f"Access denied: '{r_key}' restricted to allowed addresses"
|
|
4603
|
+
)
|
|
4604
|
+
|
|
4605
|
+
elif op_name == "LEDGER_APPEND":
|
|
4606
|
+
entry = stack.pop() if stack else None
|
|
4607
|
+
if isinstance(entry, dict) and "timestamp" not in entry:
|
|
4608
|
+
entry["timestamp"] = time.time()
|
|
4609
|
+
self.env.setdefault("_ledger", []).append(entry)
|
|
4610
|
+
|
|
4611
|
+
elif op_name in ("PARALLEL_START", "PARALLEL_END"):
|
|
4612
|
+
# Marker ops for parallel execution - no-op in stack VM
|
|
4613
|
+
pass
|
|
4614
|
+
|
|
4615
|
+
else:
|
|
4616
|
+
if debug: print(f"[VM] Unknown Opcode: {op}")
|
|
4617
|
+
|
|
4618
|
+
# Record instruction timing (if profiling enabled)
|
|
4619
|
+
if instr_start_time is not None and self.profiler:
|
|
4620
|
+
elapsed = time.perf_counter() - instr_start_time
|
|
4621
|
+
self.profiler.measure_instruction(current_ip, elapsed)
|
|
4622
|
+
except Exception as e:
|
|
4623
|
+
if self.env.get("_continue_on_error", False):
|
|
4624
|
+
# Error Recovery Mode
|
|
4625
|
+
if debug: print(f"[VM ERROR RECOVERY] {e}")
|
|
4626
|
+
self.env.setdefault("_errors", []).append(str(e))
|
|
4627
|
+
else:
|
|
4628
|
+
raise
|
|
1681
4629
|
|
|
1682
4630
|
if profile_ops and opcode_counts is not None:
|
|
1683
4631
|
self._last_opcode_profile = sorted(opcode_counts.items(), key=lambda item: item[1], reverse=True)
|
|
@@ -1701,19 +4649,21 @@ class VM:
|
|
|
1701
4649
|
|
|
1702
4650
|
local_env = {k: v for k, v in zip(params, args)}
|
|
1703
4651
|
|
|
1704
|
-
inner_vm = VM(
|
|
1705
|
-
|
|
1706
|
-
env=local_env
|
|
1707
|
-
parent_env=parent_env,
|
|
1708
|
-
# Inherit configuration
|
|
1709
|
-
use_jit=self.use_jit,
|
|
1710
|
-
use_memory_manager=self.use_memory_manager
|
|
4652
|
+
inner_vm = VM.create_child(
|
|
4653
|
+
parent_vm=parent_env if isinstance(parent_env, VM) else self,
|
|
4654
|
+
env=local_env
|
|
1711
4655
|
)
|
|
4656
|
+
if not isinstance(parent_env, VM):
|
|
4657
|
+
inner_vm._parent_env = parent_env
|
|
4658
|
+
|
|
1712
4659
|
snapshot = fn.get("closure_snapshot")
|
|
1713
4660
|
if snapshot:
|
|
1714
4661
|
for key, value in snapshot.items():
|
|
1715
4662
|
inner_vm._closure_cells[key] = Cell(value)
|
|
1716
|
-
|
|
4663
|
+
try:
|
|
4664
|
+
return await inner_vm._run_stack_bytecode(func_bc, debug=False)
|
|
4665
|
+
finally:
|
|
4666
|
+
self._return_vm_to_pool(inner_vm)
|
|
1717
4667
|
|
|
1718
4668
|
# 2. Python Callable / Builtin Wrapper
|
|
1719
4669
|
return await self._call_builtin_async_obj(fn, args)
|
|
@@ -1738,6 +4688,50 @@ class VM:
|
|
|
1738
4688
|
|
|
1739
4689
|
# Extract .fn if it's a wrapper
|
|
1740
4690
|
real_fn = fn_obj.fn if hasattr(fn_obj, "fn") else fn_obj
|
|
4691
|
+
|
|
4692
|
+
# Execute Zexus Action/LambdaFunction via VM if possible, fallback to evaluator
|
|
4693
|
+
try:
|
|
4694
|
+
ZAction, ZLambda = _get_action_types()
|
|
4695
|
+
if ZAction is not None and isinstance(real_fn, (ZAction, ZLambda)):
|
|
4696
|
+
# Try to compile to bytecode and execute in VM (fast path)
|
|
4697
|
+
action_bytecode = None
|
|
4698
|
+
try:
|
|
4699
|
+
if hasattr(real_fn, '_cached_bytecode'):
|
|
4700
|
+
action_bytecode = real_fn._cached_bytecode
|
|
4701
|
+
else:
|
|
4702
|
+
from ..evaluator.bytecode_compiler import EvaluatorBytecodeCompiler
|
|
4703
|
+
compiler = EvaluatorBytecodeCompiler(use_cache=False)
|
|
4704
|
+
action_bytecode = compiler.compile(real_fn.body, optimize=True)
|
|
4705
|
+
if action_bytecode and not compiler.errors:
|
|
4706
|
+
real_fn._cached_bytecode = action_bytecode
|
|
4707
|
+
except Exception:
|
|
4708
|
+
action_bytecode = None
|
|
4709
|
+
|
|
4710
|
+
if action_bytecode:
|
|
4711
|
+
# Execute via VM (fast)
|
|
4712
|
+
call_args = [self._wrap_for_builtin(arg) for arg in args] if wrap_args else list(args)
|
|
4713
|
+
params = real_fn.parameters if hasattr(real_fn, 'parameters') else []
|
|
4714
|
+
local_env = {k.value if hasattr(k, 'value') else k: v for k, v in zip(params, call_args)}
|
|
4715
|
+
inner_vm = VM.create_child(parent_vm=self, env=local_env)
|
|
4716
|
+
try:
|
|
4717
|
+
result = inner_vm._run_stack_bytecode_sync(action_bytecode, debug=False)
|
|
4718
|
+
finally:
|
|
4719
|
+
self._return_vm_to_pool(inner_vm)
|
|
4720
|
+
return self._unwrap_after_builtin(result)
|
|
4721
|
+
else:
|
|
4722
|
+
# Fallback to interpreter (slow)
|
|
4723
|
+
from ..evaluator.core import Evaluator
|
|
4724
|
+
if self._action_evaluator is None:
|
|
4725
|
+
self._action_evaluator = Evaluator(use_vm=False)
|
|
4726
|
+
call_args = [self._wrap_for_builtin(arg) for arg in args] if wrap_args else list(args)
|
|
4727
|
+
result = self._action_evaluator.apply_function(real_fn, call_args)
|
|
4728
|
+
trace_errors = os.environ.get("ZEXUS_VM_TRACE_ERRORS")
|
|
4729
|
+
if trace_errors and trace_errors.lower() not in ("0", "false", "off"):
|
|
4730
|
+
if isinstance(result, ZEvaluationError):
|
|
4731
|
+
print(f"[VM TRACE] action error: {result.message}")
|
|
4732
|
+
return self._unwrap_after_builtin(result)
|
|
4733
|
+
except Exception:
|
|
4734
|
+
pass
|
|
1741
4735
|
|
|
1742
4736
|
if not callable(real_fn): return real_fn
|
|
1743
4737
|
|
|
@@ -1748,11 +4742,18 @@ class VM:
|
|
|
1748
4742
|
fn_name = getattr(fn_obj, "name", getattr(real_fn, "__name__", "<callable>"))
|
|
1749
4743
|
print(f"[VM DEBUG] calling builtin {fn_name} args={[type(a).__name__ for a in call_args]}")
|
|
1750
4744
|
res = real_fn(*call_args)
|
|
4745
|
+
trace_errors = os.environ.get("ZEXUS_VM_TRACE_ERRORS")
|
|
4746
|
+
if trace_errors and trace_errors.lower() not in ("0", "false", "off"):
|
|
4747
|
+
if isinstance(res, ZEvaluationError):
|
|
4748
|
+
print(f"[VM TRACE] builtin error: {res.message}")
|
|
1751
4749
|
if verbose_active and res is None:
|
|
1752
4750
|
fn_name = getattr(fn_obj, "name", getattr(real_fn, "__name__", "<callable>"))
|
|
1753
4751
|
print(f"[VM DEBUG] builtin {fn_name} returned None args={call_args}")
|
|
1754
4752
|
if asyncio.iscoroutine(res) or isinstance(res, asyncio.Future):
|
|
1755
|
-
|
|
4753
|
+
if self.async_optimizer:
|
|
4754
|
+
res = await self.async_optimizer.await_optimized(res)
|
|
4755
|
+
else:
|
|
4756
|
+
res = await res
|
|
1756
4757
|
return self._unwrap_after_builtin(res)
|
|
1757
4758
|
except Exception as e:
|
|
1758
4759
|
return e
|
|
@@ -1782,7 +4783,7 @@ class VM:
|
|
|
1782
4783
|
results = {'iterations': iterations, 'modes': {}}
|
|
1783
4784
|
|
|
1784
4785
|
# Stack
|
|
1785
|
-
def run_stack(): return
|
|
4786
|
+
def run_stack(): return self._run_coroutine_sync(self._execute_stack(bytecode))
|
|
1786
4787
|
t_stack = timeit.timeit(run_stack, number=iterations)
|
|
1787
4788
|
stack_avg = t_stack / iterations if iterations else 0.0
|
|
1788
4789
|
results['modes']['stack'] = {'total': t_stack, 'avg': stack_avg}
|