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.
Files changed (159) hide show
  1. package/README.md +26 -3
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1187 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1425 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +485 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +13861 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +52 -11
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3589 -588
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +30 -4
  157. package/src/zexus.egg-info/SOURCES.txt +133 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -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=1000
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(level=level)
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
- if isinstance(value, (ZInteger, ZFloat, ZString, ZBoolean, ZList, ZMap)) or value is None:
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
- if isinstance(value, bool):
502
- return ZBoolean(value)
503
- if isinstance(value, int):
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 isinstance(value, dict):
796
+ if t is dict:
512
797
  pairs = {}
513
798
  for key, val in value.items():
514
- wrapped_key = VM._wrap_for_builtin(key)
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[wrapped_key] = wrapped_val
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 asyncio.run(self._run_high_level_ops(code, debug or self.debug))
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. Stack Mode (Standard/Fallback + Async Support)
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 = asyncio.run(self._execute_stack(code, debug))
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 asyncio.run(self._run_stack_bytecode(bytecode, debug))
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
- return self._parallel_vm.execute_parallel(
643
- bytecode,
644
- initial_state={"env": self.env.copy(), "builtins": self.builtins.copy(), "parent_env": self._parent_env}
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 asyncio.run(self._run_stack_bytecode(bytecode, debug))
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: return False
654
- independent_ops = sum(1 for op, _ in instructions if op in ['LOAD_CONST', 'ADD', 'SUB', 'MUL', 'HASH_BLOCK'])
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
- arith_ops = sum(1 for op, _ in instructions if op in ['ADD', 'SUB', 'MUL', 'DIV', 'EQ', 'LT'])
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
- with self._jit_lock:
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
- with self._jit_lock:
679
- # Double-check it hasn't been compiled by another thread
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
- try:
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
- # ==================== Core Execution: Stack Bytecode ====================
1737
+ # ==================== Rust VM Adaptive Routing (Phase 3) ====================
896
1738
 
897
- async def _run_stack_bytecode(self, bytecode, debug=False):
898
- # 1. JIT Check (with thread safety)
899
- if self.use_jit and self.jit_compiler:
900
- with self._jit_lock:
901
- bytecode_hash = self.jit_compiler._hash_bytecode(bytecode)
902
- jit_function = self.jit_compiler.compilation_cache.get(bytecode_hash)
903
-
904
- if jit_function:
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
- start_t = time.perf_counter()
907
- stack = []
908
- result = jit_function(self, stack, self.env)
909
- with self._jit_lock:
910
- self.jit_compiler.stats.cache_hits += 1
911
- self.jit_compiler.record_execution_time(bytecode_hash, time.perf_counter() - start_t, ExecutionTier.JIT_NATIVE)
912
- if debug: print(f"[VM JIT] Executed cached function")
913
- return result
914
- except Exception as e:
915
- if debug: print(f"[VM JIT] Failed: {e}, falling back")
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
- # 2. Bytecode Execution Setup
918
- consts = list(getattr(bytecode, "constants", []))
919
- instrs = list(getattr(bytecode, "instructions", []))
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
- class _EvalStack:
932
- __slots__ = ("data", "sp")
1851
+ return result_dict.get("result")
933
1852
 
934
- def __init__(self, capacity: int):
935
- base = max(32, capacity)
936
- self.data = [None] * base
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
- def _ensure_capacity(self):
940
- if self.sp >= len(self.data):
941
- self.data.extend([None] * len(self.data))
1857
+ # ==================== Fast Synchronous Dispatch (Performance Mode) ====================
942
1858
 
943
- def append(self, value: Any):
944
- self._ensure_capacity()
945
- self.data[self.sp] = value
946
- self.sp += 1
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
- def pop(self):
949
- if self.sp == 0:
950
- raise IndexError("pop from empty stack")
951
- self.sp -= 1
952
- value = self.data[self.sp]
953
- self.data[self.sp] = None
954
- return value
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
- if name in self.env:
988
- val = self.env[name]
989
- return val.value if isinstance(val, Cell) else val
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
- if name in self._closure_cells:
992
- return self._closure_cells[name].value
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
- if name in p.env:
998
- val = p.env[name]
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
- if name in p._closure_cells:
1001
- return p._closure_cells[name].value
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: return p[name]
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; return
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; return
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; return
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; return
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; return
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; return
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
- stack.append(None)
2967
+ stack_append(None)
1059
2968
  return
1060
2969
  name_idx, arg_count = operand
1061
2970
  func_name = const(name_idx)
1062
- args = [stack.pop() for _ in range(arg_count)][::-1] if arg_count else []
1063
- fn = _resolve(func_name) or self.builtins.get(func_name)
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
- stack.append(fallback_res)
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
- stack.append(res)
2993
+ stack_append(res)
1070
2994
 
1071
2995
  async def _op_call_top(arg_count):
1072
2996
  count = arg_count or 0
1073
- args = [stack.pop() for _ in range(count)][::-1] if count else []
1074
- fn_obj = stack.pop() if stack else None
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
- args = [stack.pop() for _ in range(arg_count)][::-1] if arg_count else []
1085
- target = stack.pop() if stack else None
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
- stack.append(None)
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 hasattr(target, "call_method"):
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
- result = target.call_method(method_name, wrapped_args)
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 = getattr(target, method_name, None)
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
- stack.append(self._unwrap_after_builtin(result))
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
- stack.append(const(idx))
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
- stack.append(_resolve(name))
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 = stack.pop() if stack else None
1123
- _store(name, val)
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
- stack.pop()
3234
+ stack_pop()
1130
3235
 
1131
3236
  def _op_dup(_):
1132
3237
  if stack:
1133
- stack.append(stack[-1])
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
- if not cond:
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
- elements = [stack.pop() for _ in range(total)][::-1]
1161
- stack.append(elements)
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 = stack.pop(); key = stack.pop()
3297
+ val = stack_pop() if stack else None
3298
+ key = stack_pop() if stack else None
1168
3299
  result[key] = val
1169
- stack.append(result)
3300
+ stack_append(result)
1170
3301
 
1171
3302
  def _op_index(_):
1172
- idx = stack.pop(); obj = stack.pop()
3303
+ idx = stack.pop() if stack else None
3304
+ obj = stack.pop() if stack else None
1173
3305
  try:
1174
- stack.append(obj[idx] if obj is not None else None)
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": _binary_op(lambda a, b: a + b),
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
- "GET_LENGTH": _op_get_length,
1220
- }
1221
- async_dispatch_ops = {"CALL_NAME", "CALL_TOP", "CALL_METHOD"}
1222
-
1223
- # 3. Execution Loop
1224
- prev_ip = None
1225
- while running and ip < len(instrs):
1226
- op, operand = instrs[ip]
1227
-
1228
- # Handle Opcode enum: convert to name for comparison
1229
- if hasattr(op, 'name'): # Opcode IntEnum
1230
- op_name = op.name
1231
- else: # Already a string
1232
- op_name = op
1233
-
1234
- if profile_ops:
1235
- opcode_counts[op_name] = opcode_counts.get(op_name, 0) + 1
1236
-
1237
- if debug: print(f"[VM SL] ip={ip} op={op} operand={operand} stack={stack.snapshot()}")
1238
-
1239
- # Profile instruction (if enabled) - start timing
1240
- instr_start_time = None
1241
- if self.enable_profiling and self.profiler and self.profiler.enabled:
1242
- if self.profiler.level in (ProfilingLevel.DETAILED, ProfilingLevel.FULL):
1243
- instr_start_time = time.perf_counter()
1244
- # Record instruction (count only for BASIC level)
1245
- self.profiler.record_instruction(ip, op_name, operand, prev_ip, len(stack))
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
- res = await self._invoke_callable_or_funcdesc(fn, args)
1342
- stack.append(res)
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
- # --- Control Flow ---
1405
- elif op_name == "JUMP":
1406
- ip = operand
1407
- elif op_name == "JUMP_IF_FALSE":
1408
- cond = stack.pop() if stack else None
1409
- if not cond: ip = operand
1410
- elif op_name == "RETURN":
1411
- return stack.pop() if stack else None
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
- # --- Collections ---
1414
- elif op_name == "BUILD_LIST":
1415
- count = operand if operand is not None else 0
1416
- elements = [stack.pop() for _ in range(count)][::-1]
1417
- stack.append(elements)
1418
- elif op_name == "BUILD_MAP":
1419
- count = operand if operand is not None else 0
1420
- result = {}
1421
- for _ in range(count):
1422
- val = stack.pop(); key = stack.pop()
1423
- result[key] = val
1424
- stack.append(result)
1425
- elif op_name == "INDEX":
1426
- idx = stack.pop(); obj = stack.pop()
1427
- try: stack.append(obj[idx] if obj is not None else None)
1428
- except (IndexError, KeyError, TypeError): stack.append(None)
1429
- elif op_name == "GET_LENGTH":
1430
- obj = stack.pop()
1431
- try:
1432
- if obj is None:
1433
- stack.append(0)
1434
- elif hasattr(obj, '__len__'):
1435
- stack.append(len(obj))
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
- stack.append(0)
1438
- except (TypeError, AttributeError):
1439
- stack.append(0)
1440
-
1441
- # --- Async & Events ---
1442
- elif op_name == "SPAWN":
1443
- # operand: tuple ("CALL", func_name, arg_count) OR index
1444
- task_handle = None
1445
- if isinstance(operand, tuple) and operand[0] == "CALL":
1446
- fn_name = operand[1]; arg_count = operand[2]
1447
- args = [stack.pop() for _ in range(arg_count)][::-1]
1448
- fn = self.builtins.get(fn_name) or self.env.get(fn_name)
1449
- coro = self._to_coro(fn, args)
1450
-
1451
- # Use async optimizer if available
1452
- if self.async_optimizer:
1453
- coro = self.async_optimizer.spawn(coro)
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
- self._task_counter += 1
1459
- tid = f"task_{self._task_counter}"
1460
- self._tasks[tid] = task
1461
- task_handle = tid
1462
- stack.append(task_handle)
1463
-
1464
- elif op_name == "AWAIT":
1465
- # Keep popping until we find a task to await
1466
- result_found = False
1467
- temp_stack = self.allocate_list(0)
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
- while stack and not result_found:
1470
- top = stack.pop()
1471
-
1472
- if isinstance(top, str) and top in self._tasks:
1473
- # Use async optimizer if available
1474
- if self.async_optimizer:
1475
- res = await self.async_optimizer.await_optimized(self._tasks[top])
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
- res = await self._tasks[top]
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
- stack.append(res)
1482
- result_found = True
1483
- elif asyncio.iscoroutine(top) or isinstance(top, asyncio.Future):
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
- res = await self.async_optimizer.await_optimized(top)
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
- res = await top
1489
- # Push back any non-task values we skipped
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
- stack.append(res)
1493
- result_found = True
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
- # Not a task, save it and keep looking
1496
- temp_stack.append(top)
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
- elif op_name == "IMPORT":
1543
- mod_name = const(operand[0])
1544
- alias = const(operand[1]) if isinstance(operand, (list,tuple)) and len(operand) > 1 else None
1545
- try:
1546
- mod = importlib.import_module(mod_name)
1547
- self.env[alias or mod_name] = mod
1548
- except Exception:
1549
- self.env[alias or mod_name] = None
1550
-
1551
- elif op_name == "DEFINE_ENUM":
1552
- enum_name = _unwrap(const(operand[0]))
1553
- enum_map = const(operand[1])
1554
- self.env.setdefault("enums", {})[enum_name] = enum_map
1555
- self.env[enum_name] = enum_map
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
- elif op_name == "VERIFY_SIGNATURE":
1579
- if len(stack) >= 3:
1580
- pk = stack.pop(); msg = stack.pop(); sig = stack.pop()
1581
- verify_fn = self.builtins.get("verify_sig") or self.env.get("verify_sig")
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
- # Fallback for testing
1587
- expected = hashlib.sha256(str(msg).encode()).hexdigest()
1588
- stack.append(sig == expected)
1589
- else:
1590
- stack.append(False)
1591
-
1592
- elif op_name == "MERKLE_ROOT":
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
- elif op_name == "TX_COMMIT":
1640
- if self.env.get("_in_transaction", False):
1641
- self.env.setdefault("_blockchain_state", {}).update(self.env.get("_tx_pending_state", {}))
1642
- self.env["_in_transaction"] = False
1643
- self.env["_tx_pending_state"] = {}
1644
- if "_tx_memory_snapshot" in self.env: del self.env["_tx_memory_snapshot"]
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
- elif op_name == "TX_REVERT":
1647
- if self.env.get("_in_transaction", False):
1648
- self.env["_blockchain_state"] = dict(self.env.get("_tx_snapshot", {}))
1649
- self.env["_in_transaction"] = False
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
- if self.use_memory_manager and "_tx_memory_snapshot" in self.env:
1652
- self._managed_objects = dict(self.env["_tx_memory_snapshot"])
1653
-
1654
- elif op_name == "GAS_CHARGE":
1655
- amount = operand if operand is not None else 0
1656
- current = self.env.get("_gas_remaining", float('inf'))
1657
- if current != float('inf'):
1658
- new_gas = current - amount
1659
- if new_gas < 0:
1660
- # Revert if in TX
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
- stack.append({"error": "OutOfGas", "required": amount, "remaining": current})
1665
- return stack[-1]
1666
- self.env["_gas_remaining"] = new_gas
1667
-
1668
- elif op_name == "LEDGER_APPEND":
1669
- entry = stack.pop() if stack else None
1670
- if isinstance(entry, dict) and "timestamp" not in entry:
1671
- entry["timestamp"] = time.time()
1672
- self.env.setdefault("_ledger", []).append(entry)
1673
-
1674
- else:
1675
- if debug: print(f"[VM] Unknown Opcode: {op}")
1676
-
1677
- # Record instruction timing (if profiling enabled)
1678
- if instr_start_time is not None and self.profiler:
1679
- elapsed = time.perf_counter() - instr_start_time
1680
- self.profiler.measure_instruction(ip, elapsed)
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
- builtins=self.builtins,
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
- return await inner_vm._run_stack_bytecode(func_bc, debug=False)
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
- res = await res
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 asyncio.run(self._execute_stack(bytecode))
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}