zexus 1.7.1 → 1.7.2

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 +3 -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 +1160 -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 +1019 -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 +421 -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 +15743 -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 +50 -9
  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 +3411 -573
  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 +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. package/src/zexus.egg-info/requires.txt +4 -0
@@ -6,9 +6,17 @@ This system unifies the evaluator and VM, automatically switching based on workl
6
6
  - Workloads >= 500: Automatic VM compilation (100x speedup)
7
7
 
8
8
  NO FLAGS NEEDED - The system automatically chooses the best execution method.
9
+
10
+ Features:
11
+ - Automatic hot loop detection
12
+ - Seamless VM compilation
13
+ - File-based persistent caching (faster repeat runs)
14
+ - Pattern-based bytecode reuse
15
+ - JIT compilation for ultra-hot paths
16
+ - Parallel execution for suitable workloads
9
17
  """
10
18
 
11
- from typing import Any, Dict, Optional
19
+ from typing import Any, Dict, Optional, List
12
20
  import time
13
21
  import os
14
22
 
@@ -33,14 +41,18 @@ class WorkloadDetector:
33
41
  self.function_calls: Dict[str, int] = {} # func_name -> call count
34
42
  self.hot_functions: set = set()
35
43
 
36
- # Workload classification
37
- self.vm_threshold = 500 # Iterations before VM compilation
44
+ # Workload classification thresholds (tuned for faster VM promotion)
45
+ self.vm_threshold = 120 # Iterations before VM compilation
46
+ self.jit_threshold = 1200 # Iterations before JIT compilation
47
+ self.parallel_threshold = 6000 # Iterations before parallel execution
38
48
 
39
49
  # Statistics
40
50
  self.stats = {
41
51
  "total_loops": 0,
42
52
  "hot_loops": 0,
53
+ "jit_loops": 0,
43
54
  "vm_compilations": 0,
55
+ "jit_compilations": 0,
44
56
  "vm_executions": 0,
45
57
  "interpretation_time_ms": 0,
46
58
  "vm_time_ms": 0
@@ -48,14 +60,17 @@ class WorkloadDetector:
48
60
 
49
61
  def track_loop_iteration(self, loop_id: int) -> Dict[str, Any]:
50
62
  """
51
- Track loop iteration and determine if VM should be used.
63
+ Track loop iteration and determine optimal execution strategy.
52
64
 
53
65
  Returns:
54
66
  {
55
- "should_compile": bool, # Should compile to VM now
56
- "use_vm": bool, # Should use already-compiled VM
57
- "iteration": int, # Current iteration number
58
- "is_hot": bool # Is this a hot loop
67
+ "should_compile": bool, # Should compile to VM now
68
+ "should_jit": bool, # Should JIT compile now
69
+ "use_vm": bool, # Should use already-compiled VM
70
+ "use_jit": bool, # Should use JIT-compiled code
71
+ "iteration": int, # Current iteration number
72
+ "is_hot": bool, # Is this a hot loop
73
+ "tier": str # Current execution tier
59
74
  }
60
75
  """
61
76
  if loop_id not in self.loop_iterations:
@@ -66,20 +81,33 @@ class WorkloadDetector:
66
81
  self.loop_iterations[loop_id] += 1
67
82
  iteration = self.loop_iterations[loop_id]
68
83
 
69
- # Check if we just hit the threshold
84
+ # Determine execution tier
70
85
  should_compile = (iteration == self.vm_threshold)
71
-
72
- # Check if we're past the threshold (VM should be available)
86
+ should_jit = (iteration == self.jit_threshold)
73
87
  is_hot = (iteration >= self.vm_threshold)
88
+ is_jit_hot = (iteration >= self.jit_threshold)
74
89
 
75
90
  if should_compile:
76
91
  self.stats["hot_loops"] += 1
92
+ if should_jit:
93
+ self.stats["jit_loops"] += 1
94
+
95
+ # Determine tier
96
+ if is_jit_hot:
97
+ tier = "jit"
98
+ elif is_hot:
99
+ tier = "vm"
100
+ else:
101
+ tier = "interpreted"
77
102
 
78
103
  return {
79
104
  "should_compile": should_compile,
80
- "use_vm": is_hot,
105
+ "should_jit": should_jit,
106
+ "use_vm": is_hot and not is_jit_hot,
107
+ "use_jit": is_jit_hot,
81
108
  "iteration": iteration,
82
- "is_hot": is_hot
109
+ "is_hot": is_hot,
110
+ "tier": tier
83
111
  }
84
112
 
85
113
  def track_function_call(self, func_name: str) -> bool:
@@ -130,7 +158,8 @@ class WorkloadDetector:
130
158
  (self.stats["vm_time_ms"] / total_time * 100) if total_time > 0 else 0
131
159
  ),
132
160
  "active_hot_functions": len(self.hot_functions),
133
- "threshold": self.vm_threshold
161
+ "vm_threshold": self.vm_threshold,
162
+ "jit_threshold": self.jit_threshold
134
163
  }
135
164
 
136
165
 
@@ -141,6 +170,9 @@ class UnifiedExecutor:
141
170
  Features:
142
171
  - Automatic workload detection
143
172
  - Transparent VM compilation
173
+ - JIT compilation for ultra-hot paths
174
+ - File-based persistent caching
175
+ - Pattern-based bytecode reuse
144
176
  - Environment synchronization
145
177
  - Zero-overhead switching
146
178
  """
@@ -157,24 +189,154 @@ class UnifiedExecutor:
157
189
  # Workload detector
158
190
  self.workload = WorkloadDetector()
159
191
 
160
- # VM compilation cache
192
+ # VM compilation cache (uses shared persistent cache)
161
193
  self.compiled_loops: Dict[int, Any] = {} # loop_id -> bytecode
162
194
  self.compiled_functions: Dict[str, Any] = {} # func_name -> bytecode
195
+ self.jit_compiled: Dict[int, Any] = {} # loop_id -> native code
196
+ self._force_vm_loops: set[int] = set() # loop_ids promoted immediately
197
+ self._loop_sync_keys: Dict[int, set[str]] = {}
198
+ self.force_all_vm_loops = False
199
+ self._compile_errors: Dict[int, Any] = {}
200
+ self._compile_info: Dict[int, Any] = {}
201
+ self._logged_vm_loops: set[int] = set()
202
+ self._logged_vm_loop_env: set[int] = set()
163
203
 
164
204
  # VM instance (lazy init)
165
205
  self.vm = None
166
206
  self._profile_reported = False
207
+ self._vm_action_child = None
208
+
209
+ # VM configuration overrides (applied on next VM init)
210
+ self.vm_config: Dict[str, Any] = {
211
+ "mode": "auto",
212
+ "use_jit": True,
213
+ "max_heap_mb": 1024,
214
+ "debug": False,
215
+ "use_memory_manager": False,
216
+ "gc_threshold": 1000,
217
+ "enable_gas_metering": True,
218
+ "enable_peephole_optimizer": True,
219
+ "enable_bytecode_optimizer": False,
220
+ "optimizer_level": 2,
221
+ "enable_memory_pool": True,
222
+ "enable_async_optimizer": True,
223
+ "enable_profiling": False,
224
+ "profiling_level": "DETAILED",
225
+ "profiling_sample_rate": 1.0,
226
+ "profiling_max_samples": 2048,
227
+ "profiling_track_overhead": False,
228
+ "enable_ssa": False,
229
+ "enable_register_allocation": False,
230
+ "enable_bytecode_converter": False,
231
+ "converter_aggressive": False,
232
+ "vm_full_loop": True,
233
+ "vm_sync_all": False,
234
+ "vm_allow_unsafe_loops": False,
235
+ "vm_dump_bytecode": False,
236
+ "vm_single_shot": False,
237
+ "vm_single_shot_min_instructions": 24,
238
+ "fast_single_shot": False,
239
+ "single_shot_max_instructions": 64,
240
+ "vm_action_cache": False,
241
+ "vm_action_sync_all": False,
242
+ "perf_disable_gas_metering": False,
243
+ "perf_fast_dispatch": False,
244
+ }
245
+
246
+ # JIT compiler (lazy init)
247
+ self.jit_compiler = None
167
248
 
168
249
  # Compilation failures (don't retry)
169
250
  self.failed_compilations: set = set()
170
251
 
252
+ # Get shared cache for persistence
253
+ self._shared_cache = None
254
+ try:
255
+ from .bytecode_compiler import get_shared_cache
256
+ self._shared_cache = get_shared_cache()
257
+ except ImportError:
258
+ pass
259
+
171
260
  # Statistics
172
261
  self.stats = {
173
262
  "total_executions": 0,
174
263
  "vm_hits": 0,
264
+ "jit_hits": 0,
175
265
  "compilation_failures": 0,
176
- "environment_syncs": 0
266
+ "environment_syncs": 0,
267
+ "cache_hits": 0
177
268
  }
269
+
270
+ def configure_vm(self, config: Dict[str, Any]) -> None:
271
+ """Apply VM configuration overrides for future VM initialization."""
272
+ if not isinstance(config, dict):
273
+ return
274
+ for key, value in config.items():
275
+ self.vm_config[key] = value
276
+ # Reset VM so config changes take effect
277
+ self.vm = None
278
+
279
+ def reset_vm(self) -> None:
280
+ """Drop the cached VM instance to force reinitialization."""
281
+ self.vm = None
282
+
283
+ def set_force_vm_loops(self, flag: bool) -> None:
284
+ """Force all loops to attempt VM compilation/execution (debug/profiling)."""
285
+ self.force_all_vm_loops = bool(flag)
286
+
287
+ def ensure_vm(self, profile_active: bool = False) -> None:
288
+ """Ensure a VM instance is initialized with current configuration."""
289
+ if self.vm is not None:
290
+ return
291
+ from ..vm.vm import VM, VMMode
292
+ gas_limit = 10_000_000
293
+ if profile_active:
294
+ profile_limit_env = os.environ.get("ZEXUS_VM_PROFILE_GAS_LIMIT")
295
+ if profile_limit_env is not None:
296
+ try:
297
+ gas_limit = int(profile_limit_env)
298
+ except ValueError:
299
+ gas_limit = 50_000_000
300
+ else:
301
+ gas_limit = 50_000_000
302
+
303
+ mode_value = self.vm_config.get("mode", "auto")
304
+ if isinstance(mode_value, str):
305
+ mode = getattr(VMMode, mode_value.upper(), VMMode.AUTO)
306
+ else:
307
+ mode = mode_value
308
+ self.vm = VM(
309
+ mode=mode,
310
+ use_jit=bool(self.vm_config.get("use_jit", True)),
311
+ max_heap_mb=int(self.vm_config.get("max_heap_mb", 1024)),
312
+ debug=bool(self.vm_config.get("debug", False)),
313
+ use_memory_manager=bool(self.vm_config.get("use_memory_manager", False)),
314
+ gc_threshold=int(self.vm_config.get("gc_threshold", 1000)),
315
+ gas_limit=gas_limit,
316
+ enable_gas_metering=bool(self.vm_config.get("enable_gas_metering", True)),
317
+ enable_peephole_optimizer=bool(self.vm_config.get("enable_peephole_optimizer", True)),
318
+ enable_bytecode_optimizer=bool(self.vm_config.get("enable_bytecode_optimizer", False)),
319
+ optimizer_level=int(self.vm_config.get("optimizer_level", 2)),
320
+ enable_memory_pool=bool(self.vm_config.get("enable_memory_pool", True)),
321
+ enable_async_optimizer=bool(self.vm_config.get("enable_async_optimizer", True)),
322
+ enable_profiling=bool(self.vm_config.get("enable_profiling", False)),
323
+ profiling_level=str(self.vm_config.get("profiling_level", "DETAILED")),
324
+ profiling_sample_rate=float(self.vm_config.get("profiling_sample_rate", 1.0)),
325
+ profiling_max_samples=int(self.vm_config.get("profiling_max_samples", 2048)),
326
+ profiling_track_overhead=bool(self.vm_config.get("profiling_track_overhead", False)),
327
+ enable_ssa=bool(self.vm_config.get("enable_ssa", False)),
328
+ enable_register_allocation=bool(self.vm_config.get("enable_register_allocation", False)),
329
+ enable_bytecode_converter=bool(self.vm_config.get("enable_bytecode_converter", False)),
330
+ converter_aggressive=bool(self.vm_config.get("converter_aggressive", False)),
331
+ fast_single_shot=bool(self.vm_config.get("fast_single_shot", False)),
332
+ single_shot_max_instructions=int(self.vm_config.get("single_shot_max_instructions", 64)),
333
+ )
334
+ # Apply perf flags after construction
335
+ if bool(self.vm_config.get("perf_disable_gas_metering", False)):
336
+ self.vm.enable_gas_metering = False
337
+ self.vm.gas_metering = None
338
+ if bool(self.vm_config.get("perf_fast_dispatch", False)):
339
+ self.vm._perf_fast_dispatch = True
178
340
 
179
341
  def execute_loop(self, loop_id: int, condition_node, body_node, env, stack_trace) -> Any:
180
342
  """
@@ -197,14 +359,39 @@ class UnifiedExecutor:
197
359
  Loop result
198
360
  """
199
361
  from ..object import NULL, EvaluationError
200
- profile_flag = os.environ.get("ZEXUS_VM_PROFILE_OPS")
201
- profile_active = profile_flag and profile_flag.lower() not in ("0", "false", "off")
202
- verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
203
- profile_verbose = profile_active and verbose_flag and verbose_flag.lower() not in ("0", "false", "off")
204
362
  from ..evaluator.utils import is_error, is_truthy
205
363
 
206
364
  self.stats["total_executions"] += 1
207
365
  result = NULL
366
+
367
+ # If the loop body contains constructs the VM cannot safely handle yet
368
+ # (e.g., transaction blocks or break/continue), pin this loop to interpreter only.
369
+ allow_unsafe = bool(self.vm_config.get("vm_allow_unsafe_loops", False))
370
+ if self.vm_enabled and not self.force_all_vm_loops and not allow_unsafe and not self._is_vm_safe_loop(body_node):
371
+ self.failed_compilations.add(loop_id)
372
+ self._compile_errors[loop_id] = "unsafe loop (control-flow or tx statement)"
373
+
374
+ # Fast-path: promote obviously hot loops before iteration threshold
375
+ force_vm_loop = loop_id in self._force_vm_loops or self.force_all_vm_loops
376
+ if (
377
+ self.vm_enabled
378
+ and loop_id not in self.failed_compilations
379
+ and loop_id not in self.compiled_loops
380
+ and not force_vm_loop
381
+ ):
382
+ try:
383
+ from .bytecode_compiler import should_use_vm_for_node
384
+ from .. import zexus_ast
385
+ loop_node = zexus_ast.WhileStatement(condition_node, body_node)
386
+
387
+ if should_use_vm_for_node(body_node):
388
+ full_loop_mode = bool(self.vm_config.get("vm_full_loop", True))
389
+ promoted = self._compile_loop(loop_id, loop_node, env, full_loop=full_loop_mode)
390
+ if promoted:
391
+ self._force_vm_loops.add(loop_id)
392
+ force_vm_loop = True
393
+ except ImportError:
394
+ force_vm_loop = loop_id in self._force_vm_loops
208
395
 
209
396
  while True:
210
397
  # CRITICAL: Resource limit check (prevents infinite loops)
@@ -220,6 +407,15 @@ class UnifiedExecutor:
220
407
 
221
408
  # Track iteration
222
409
  info = self.workload.track_loop_iteration(loop_id)
410
+ force_vm_loop = loop_id in self._force_vm_loops or force_vm_loop or self.force_all_vm_loops
411
+ single_shot_active = False
412
+
413
+ # Promote to parallel VM mode for very hot loops
414
+ if (
415
+ self.vm_enabled
416
+ and info["iteration"] == self.workload.parallel_threshold
417
+ ):
418
+ self.configure_vm({"mode": "parallel"})
223
419
 
224
420
  # Check condition
225
421
  cond = self.evaluator.eval_node(condition_node, env, stack_trace)
@@ -232,9 +428,20 @@ class UnifiedExecutor:
232
428
  break
233
429
 
234
430
  # Decide execution method
235
- if info["should_compile"] and self.vm_enabled:
431
+ if (
432
+ info["should_compile"]
433
+ and self.vm_enabled
434
+ and loop_id not in self.compiled_loops
435
+ and loop_id not in self.failed_compilations
436
+ ):
236
437
  # Just hit threshold - compile now
237
- success = self._compile_loop(loop_id, body_node, env)
438
+ try:
439
+ from .. import zexus_ast
440
+ loop_node = zexus_ast.WhileStatement(condition_node, body_node)
441
+ except Exception:
442
+ loop_node = body_node
443
+ full_loop_mode = bool(self.vm_config.get("vm_full_loop", True))
444
+ success = self._compile_loop(loop_id, loop_node, env, full_loop=full_loop_mode)
238
445
 
239
446
  if success:
240
447
  # Compilation succeeded - will use VM from now on
@@ -242,17 +449,68 @@ class UnifiedExecutor:
242
449
  else:
243
450
  # Compilation failed - mark and continue with interpreter
244
451
  self.failed_compilations.add(loop_id)
452
+ if loop_id not in self._compile_errors:
453
+ self._compile_errors[loop_id] = "compile returned false"
454
+
455
+ # Single-shot VM: allow VM execution for one-iteration loops
456
+ if (
457
+ not info["use_vm"]
458
+ and self.vm_enabled
459
+ and bool(self.vm_config.get("vm_single_shot", False))
460
+ and info["iteration"] == 1
461
+ and loop_id not in self.compiled_loops
462
+ and loop_id not in self.failed_compilations
463
+ ):
464
+ try:
465
+ from .bytecode_compiler import should_use_vm_for_node
466
+ if should_use_vm_for_node(body_node):
467
+ success = self._compile_loop(loop_id, body_node, env, full_loop=False)
468
+ if success:
469
+ instr_count = self._compile_info.get(loop_id, {}).get("instructions", 0)
470
+ min_instr = int(self.vm_config.get("vm_single_shot_min_instructions", 24))
471
+ if instr_count >= min_instr:
472
+ single_shot_active = True
473
+ else:
474
+ # Too small to justify VM overhead
475
+ self.compiled_loops.pop(loop_id, None)
476
+ except Exception:
477
+ pass
245
478
 
246
479
  # Execute body
247
- if info["use_vm"] and loop_id in self.compiled_loops and loop_id not in self.failed_compilations:
480
+ use_vm_now = (
481
+ loop_id in self.compiled_loops
482
+ and loop_id not in self.failed_compilations
483
+ and (force_vm_loop or info["use_vm"] or single_shot_active)
484
+ )
485
+
486
+ if use_vm_now:
487
+ sync_keys = None
488
+ if not bool(self.vm_config.get("vm_sync_all", False)):
489
+ sync_keys = self._loop_sync_keys.get(loop_id)
490
+ if sync_keys is None:
491
+ sync_keys = self._collect_assigned_names(body_node)
492
+ self._loop_sync_keys[loop_id] = sync_keys
493
+ verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
494
+ if verbose_flag and verbose_flag.lower() not in ("0", "false", "off"):
495
+ if loop_id not in self._logged_vm_loops:
496
+ info = self._compile_info.get(loop_id, {})
497
+ instr_count = info.get("instructions", "?")
498
+ print(f"[VM TRACE] executing loop_id={loop_id} instr={instr_count}")
499
+ self._logged_vm_loops.add(loop_id)
500
+ entry = self.compiled_loops[loop_id]
501
+ is_full_loop = isinstance(entry, dict) and entry.get("full_loop") is True
248
502
  # Use VM
249
- result = self._execute_via_vm(loop_id, env)
503
+ result = self._execute_via_vm(loop_id, env, sync_keys=sync_keys)
250
504
  self.stats["vm_hits"] += 1
251
-
252
505
  if is_error(result):
253
506
  # VM execution failed - fall back to interpreter
254
507
  self.failed_compilations.add(loop_id)
508
+ self._compile_errors[loop_id] = str(result)
255
509
  result = self.evaluator.eval_node(body_node, env, stack_trace)
510
+ elif is_full_loop:
511
+ # Full loop executed in VM, return immediately
512
+ self.workload.reset_loop(loop_id)
513
+ return result
256
514
  else:
257
515
  # Use interpreter
258
516
  result = self.evaluator.eval_node(body_node, env, stack_trace)
@@ -271,16 +529,18 @@ class UnifiedExecutor:
271
529
  return result if isinstance(result, ReturnValue) else NULL
272
530
 
273
531
  # Loop completed normally
274
- self.workload.reset_loop(loop_id)
275
532
  return result
276
533
 
277
- def _compile_loop(self, loop_id: int, body_node, env) -> bool:
534
+ def _compile_loop(self, loop_id: int, node_to_compile, env, full_loop: bool = False) -> bool:
278
535
  """
279
536
  Compile loop body to VM bytecode.
280
537
 
281
538
  Returns:
282
539
  True if compilation succeeded
283
540
  """
541
+ # Bail out early for loops marked unsafe
542
+ if loop_id in self.failed_compilations:
543
+ return False
284
544
  try:
285
545
  from ..vm.compiler import BytecodeCompiler
286
546
  from ..evaluator.bytecode_compiler import EvaluatorBytecodeCompiler
@@ -288,13 +548,68 @@ class UnifiedExecutor:
288
548
  profile_active = profile_flag and profile_flag.lower() not in ("0", "false", "off")
289
549
  verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
290
550
  profile_verbose = profile_active and verbose_flag and verbose_flag.lower() not in ("0", "false", "off")
291
-
551
+
292
552
  # Try evaluator's bytecode compiler first (better integration)
293
- compiler = EvaluatorBytecodeCompiler()
294
- bytecode = compiler.compile(body_node, optimize=True)
295
-
553
+ compiler = EvaluatorBytecodeCompiler(use_cache=True)
554
+ bytecode = compiler.compile(node_to_compile, optimize=True)
555
+
296
556
  if bytecode and not compiler.errors:
297
- self.compiled_loops[loop_id] = bytecode
557
+ try:
558
+ instruction_count = len(bytecode.instructions) if hasattr(bytecode, "instructions") else 0
559
+ constant_count = len(bytecode.constants) if hasattr(bytecode, "constants") else 0
560
+ ops_preview = []
561
+ if hasattr(bytecode, "instructions"):
562
+ for instr in bytecode.instructions[:40]:
563
+ if instr is None:
564
+ continue
565
+ op = instr[0] if isinstance(instr, tuple) and len(instr) >= 1 else instr
566
+ op_name = op.name if hasattr(op, "name") else str(op)
567
+ ops_preview.append(op_name)
568
+ except Exception:
569
+ instruction_count = 0
570
+ constant_count = 0
571
+ ops_preview = []
572
+ self._compile_info[loop_id] = {
573
+ "instructions": instruction_count,
574
+ "constants": constant_count,
575
+ "full_loop": bool(full_loop),
576
+ "ops_preview": ops_preview
577
+ }
578
+ dump_flag = bool(self.vm_config.get("vm_dump_bytecode", False))
579
+ env_dump = os.environ.get("ZEXUS_VM_DUMP_BYTECODE")
580
+ if env_dump and env_dump.lower() not in ("0", "false", "off"):
581
+ dump_flag = True
582
+ if dump_flag:
583
+ try:
584
+ dump_lines = []
585
+ dump_lines.append(f"Loop {loop_id} | full_loop={bool(full_loop)}")
586
+ dump_lines.append(f"Instructions: {instruction_count} | Constants: {constant_count}")
587
+ dump_lines.append("\nConstants:")
588
+ for i, const in enumerate(getattr(bytecode, "constants", [])):
589
+ dump_lines.append(f" {i:04d}: {const!r}")
590
+ dump_lines.append("\nInstructions:")
591
+ for idx, instr in enumerate(getattr(bytecode, "instructions", [])):
592
+ if instr is None:
593
+ continue
594
+ if isinstance(instr, tuple) and len(instr) >= 2:
595
+ op = instr[0]
596
+ operand = instr[1]
597
+ op_name = op.name if hasattr(op, "name") else str(op)
598
+ dump_lines.append(f" {idx:04d}: {op_name} {operand}")
599
+ else:
600
+ dump_lines.append(f" {idx:04d}: {instr}")
601
+ dump_path = f"/tmp/zexus_vm_dump_{loop_id}.txt"
602
+ with open(dump_path, "w", encoding="utf-8") as handle:
603
+ handle.write("\n".join(dump_lines))
604
+ except Exception:
605
+ pass
606
+ if full_loop:
607
+ self.compiled_loops[loop_id] = {
608
+ "bytecode": bytecode,
609
+ "full_loop": True
610
+ }
611
+ else:
612
+ self.compiled_loops[loop_id] = bytecode
298
613
  self.workload.stats["vm_compilations"] += 1
299
614
  if profile_verbose:
300
615
  print(f"[VM DEBUG] loop {loop_id} compiled to bytecode")
@@ -302,21 +617,24 @@ class UnifiedExecutor:
302
617
  print("[VM DEBUG] constants={}".format(list(enumerate(bytecode.constants))))
303
618
  return True
304
619
  else:
305
- # Compilation failed
306
620
  if profile_active:
307
621
  print(f"[VM DEBUG] loop {loop_id} compilation errors={compiler.errors}")
622
+ if compiler.errors:
623
+ self._compile_errors[loop_id] = [str(err) for err in compiler.errors]
624
+ else:
625
+ self._compile_errors[loop_id] = "compile returned no bytecode"
308
626
  self.stats["compilation_failures"] += 1
309
627
  return False
310
-
628
+
311
629
  except Exception as e:
312
- # Unexpected compilation error
313
630
  profile_flag = os.environ.get("ZEXUS_VM_PROFILE_OPS")
314
631
  if profile_flag and profile_flag.lower() not in ("0", "false", "off"):
315
632
  print(f"[VM DEBUG] loop {loop_id} compilation exception={e}")
633
+ self._compile_errors[loop_id] = str(e)
316
634
  self.stats["compilation_failures"] += 1
317
635
  return False
318
636
 
319
- def _execute_via_vm(self, loop_id: int, env) -> Any:
637
+ def _execute_via_vm(self, loop_id: int, env, sync_keys: Optional[set[str]] = None) -> Any:
320
638
  """
321
639
  Execute compiled loop via VM.
322
640
 
@@ -325,38 +643,45 @@ class UnifiedExecutor:
325
643
  """
326
644
  from ..object import NULL, EvaluationError
327
645
 
646
+ profile_flag = os.environ.get("ZEXUS_VM_PROFILE_OPS")
647
+ profile_active = profile_flag and profile_flag.lower() not in ("0", "false", "off")
648
+ verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
649
+ profile_verbose = profile_active and verbose_flag and verbose_flag.lower() not in ("0", "false", "off")
650
+
328
651
  if loop_id not in self.compiled_loops:
329
652
  return EvaluationError("Loop not compiled")
330
653
 
331
654
  try:
332
- # Lazy init VM
333
- if self.vm is None:
334
- from ..vm.vm import VM, VMMode
335
- gas_limit = 10_000_000
336
- if profile_active:
337
- profile_limit_env = os.environ.get("ZEXUS_VM_PROFILE_GAS_LIMIT")
338
- if profile_limit_env is not None:
339
- try:
340
- gas_limit = int(profile_limit_env)
341
- except ValueError:
342
- gas_limit = 50_000_000
343
- else:
344
- gas_limit = 50_000_000
345
- self.vm = VM(
346
- mode=VMMode.AUTO,
347
- use_jit=True,
348
- max_heap_mb=1024,
349
- debug=False,
350
- gas_limit=gas_limit
351
- )
352
-
655
+ # Lazy init VM with all standard optimizations enabled
656
+ self.ensure_vm(profile_active=profile_active)
657
+
353
658
  # Sync environment to VM
354
659
  self._sync_env_to_vm(env)
355
660
  if hasattr(self.evaluator, 'builtins') and self.evaluator.builtins:
356
661
  self.vm.builtins = {k: v for k, v in self.evaluator.builtins.items()}
357
-
662
+
663
+ verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
664
+ if verbose_flag and verbose_flag.lower() not in ("0", "false", "off"):
665
+ if loop_id not in self._logged_vm_loop_env:
666
+ i_val = self.vm.env.get("i")
667
+ count_val = self.vm.env.get("PERF_TRANSACTION_COUNT")
668
+ use_bulk_all = self.vm.env.get("use_bulk_all")
669
+ use_fast_blocks = self.vm.env.get("use_fast_blocks")
670
+ blockchain_val = self.vm.env.get("blockchain")
671
+ if i_val is not None or count_val is not None or use_bulk_all is not None:
672
+ chain_type = type(blockchain_val).__name__ if blockchain_val is not None else None
673
+ print(
674
+ f"[VM TRACE] loop_id={loop_id} i={i_val} PERF_TRANSACTION_COUNT={count_val} "
675
+ f"use_bulk_all={use_bulk_all} use_fast_blocks={use_fast_blocks} blockchain={chain_type}"
676
+ )
677
+ self._logged_vm_loop_env.add(loop_id)
678
+
358
679
  # Execute bytecode
359
- bytecode = self.compiled_loops[loop_id]
680
+ entry = self.compiled_loops[loop_id]
681
+ if isinstance(entry, dict):
682
+ bytecode = entry.get("bytecode")
683
+ else:
684
+ bytecode = entry
360
685
  result = self.vm.execute(bytecode, debug=False)
361
686
  if profile_active:
362
687
  profile = getattr(self.vm, "_last_opcode_profile", None)
@@ -366,7 +691,7 @@ class UnifiedExecutor:
366
691
  self._profile_reported = True
367
692
 
368
693
  # Sync environment back from VM
369
- self._sync_env_from_vm(env)
694
+ self._sync_env_from_vm(env, sync_keys=sync_keys)
370
695
 
371
696
  self.workload.stats["vm_executions"] += 1
372
697
 
@@ -374,37 +699,107 @@ class UnifiedExecutor:
374
699
  return self._convert_from_vm(result) if result is not None else NULL
375
700
 
376
701
  except Exception as e:
377
- # VM execution failed
378
702
  if profile_verbose:
379
703
  print(f"[VM DEBUG] unified execution exception={e}")
380
704
  return EvaluationError(f"VM execution error: {e}")
705
+
706
+ def execute_action_bytecode(self, bytecode, env, sync_keys: Optional[set[str]] = None) -> Any:
707
+ """Execute cached action bytecode via VM with env sync."""
708
+ from ..object import NULL, EvaluationError
709
+
710
+ profile_flag = os.environ.get("ZEXUS_VM_PROFILE_OPS")
711
+ profile_active = profile_flag and profile_flag.lower() not in ("0", "false", "off")
712
+ verbose_flag = os.environ.get("ZEXUS_VM_PROFILE_VERBOSE")
713
+ profile_verbose = profile_active and verbose_flag and verbose_flag.lower() not in ("0", "false", "off")
714
+
715
+ try:
716
+ self.ensure_vm(profile_active=profile_active)
717
+ vm_target = self.vm
718
+ # Avoid VM re-entrancy when actions are invoked from within VM execution
719
+ if getattr(self.vm, "_in_execution", 0) > 0:
720
+ if (
721
+ self._vm_action_child is None
722
+ or getattr(self._vm_action_child, "_in_execution", 0) > 0
723
+ ):
724
+ self._vm_action_child = type(self.vm).create_child(parent_vm=self.vm, env={})
725
+ vm_target = self._vm_action_child
726
+ vm_target.env.clear()
727
+
728
+ self._sync_env_to_vm(env, vm=vm_target)
729
+ if hasattr(self.evaluator, 'builtins') and self.evaluator.builtins:
730
+ vm_target.builtins = {k: v for k, v in self.evaluator.builtins.items()}
731
+
732
+ result = vm_target.execute(bytecode, debug=False)
733
+
734
+ if sync_keys is None and not bool(self.vm_config.get("vm_action_sync_all", False)):
735
+ sync_keys = None
736
+ self._sync_env_from_vm(env, sync_keys=sync_keys, vm=vm_target)
737
+ self.workload.stats["vm_executions"] += 1
738
+ self.stats["vm_hits"] += 1
739
+ return self._convert_from_vm(result) if result is not None else NULL
740
+ except Exception as e:
741
+ if profile_verbose:
742
+ print(f"[VM DEBUG] action execution exception={e}")
743
+ return EvaluationError(f"VM execution error: {e}")
381
744
 
382
- def _sync_env_to_vm(self, env):
745
+ def _sync_env_to_vm(self, env, vm=None):
383
746
  """Sync evaluator environment to VM"""
384
- if not self.vm or not env:
747
+ vm_target = vm or self.vm
748
+ if not vm_target or not env:
385
749
  return
386
750
 
387
751
  self.stats["environment_syncs"] += 1
388
752
 
389
753
  # Convert evaluator objects to VM-compatible values
390
754
  if hasattr(env, 'store'):
391
- for key, value in env.store.items():
392
- vm_value = self._convert_to_vm(value)
393
- self.vm.env[key] = vm_value
755
+ scopes = []
756
+ scope = env
757
+ while scope is not None and hasattr(scope, 'store'):
758
+ scopes.append(scope)
759
+ scope = getattr(scope, 'outer', None)
760
+ # Apply outer scopes first, then inner scopes override
761
+ for scope in reversed(scopes):
762
+ for key, value in scope.store.items():
763
+ vm_value = self._convert_to_vm(value)
764
+ vm_target.env[key] = vm_value
394
765
  elif isinstance(env, dict):
395
766
  for key, value in env.items():
396
767
  vm_value = self._convert_to_vm(value)
397
- self.vm.env[key] = vm_value
768
+ vm_target.env[key] = vm_value
769
+ try:
770
+ vm_target._bump_env_version()
771
+ except Exception:
772
+ pass
398
773
 
399
- def _sync_env_from_vm(self, env):
774
+ def _sync_env_from_vm(self, env, sync_keys: Optional[set[str]] = None, vm=None):
400
775
  """Sync VM environment back to evaluator"""
401
- if not self.vm or not env:
776
+ vm_target = vm or self.vm
777
+ if not vm_target or not env:
402
778
  return
403
779
 
404
- # Convert VM values back to evaluator objects
405
- for key, value in self.vm.env.items():
780
+ if sync_keys is not None:
781
+ if len(sync_keys) == 0:
782
+ return
783
+ # Only sync tracked keys to reduce overhead
784
+ for key in sync_keys:
785
+ if key not in vm_target.env:
786
+ continue
787
+ value = vm_target.env.get(key)
788
+ eval_value = self._convert_from_vm(value)
789
+ if hasattr(env, 'set'):
790
+ try:
791
+ env.assign(key, eval_value)
792
+ except ValueError:
793
+ env.set(key, eval_value)
794
+ elif hasattr(env, 'store'):
795
+ env.store[key] = eval_value
796
+ elif isinstance(env, dict):
797
+ env[key] = eval_value
798
+ return
799
+
800
+ # Convert VM values back to evaluator objects (full sync)
801
+ for key, value in vm_target.env.items():
406
802
  eval_value = self._convert_from_vm(value)
407
-
408
803
  if hasattr(env, 'set'):
409
804
  try:
410
805
  env.assign(key, eval_value)
@@ -418,6 +813,11 @@ class UnifiedExecutor:
418
813
 
419
814
  def _convert_to_vm(self, value: Any) -> Any:
420
815
  """Convert evaluator object to VM value"""
816
+ from ..object import List as ZList, Map as ZMap
817
+ if hasattr(value, "call_method") or hasattr(value, "get_attr"):
818
+ return value
819
+ if isinstance(value, (ZList, ZMap)):
820
+ return value
421
821
  if hasattr(value, 'value'):
422
822
  # Wrapped primitive (Integer, String, Boolean, Float)
423
823
  return value.value
@@ -437,6 +837,8 @@ class UnifiedExecutor:
437
837
  def _convert_from_vm(self, value: Any) -> Any:
438
838
  """Convert VM value to evaluator object"""
439
839
  from ..object import Integer, Float, String, Boolean, List, Map, NULL
840
+ if isinstance(value, (List, Map)):
841
+ return value
440
842
 
441
843
  if value is None:
442
844
  return NULL
@@ -458,6 +860,103 @@ class UnifiedExecutor:
458
860
  return Map(pairs)
459
861
  else:
460
862
  return value
863
+
864
+ def _collect_assigned_names(self, node) -> set[str]:
865
+ """Collect variable names assigned within a node (for selective VM sync)."""
866
+ try:
867
+ from .. import zexus_ast
868
+ except Exception:
869
+ return set()
870
+
871
+ assigned: set[str] = set()
872
+
873
+ def _walk(n) -> None:
874
+ if n is None:
875
+ return
876
+ if isinstance(n, zexus_ast.LetStatement) and hasattr(n, "name"):
877
+ name = getattr(n.name, "value", None) or str(n.name)
878
+ assigned.add(name)
879
+ elif isinstance(n, zexus_ast.ConstStatement) and hasattr(n, "name"):
880
+ name = getattr(n.name, "value", None) or str(n.name)
881
+ assigned.add(name)
882
+ elif isinstance(n, zexus_ast.MethodCallExpression):
883
+ obj = getattr(n, "object", None)
884
+ if isinstance(obj, zexus_ast.Identifier):
885
+ assigned.add(obj.value)
886
+ elif isinstance(n, zexus_ast.CallExpression):
887
+ func = getattr(n, "function", None)
888
+ if isinstance(func, zexus_ast.PropertyAccessExpression):
889
+ obj = getattr(func, "object", None)
890
+ if isinstance(obj, zexus_ast.Identifier):
891
+ assigned.add(obj.value)
892
+ elif isinstance(n, zexus_ast.AssignmentExpression):
893
+ if isinstance(n.name, zexus_ast.Identifier):
894
+ assigned.add(n.name.value)
895
+ elif isinstance(n.name, zexus_ast.PropertyAccessExpression):
896
+ obj = n.name.object
897
+ if isinstance(obj, zexus_ast.Identifier):
898
+ assigned.add(obj.value)
899
+ elif isinstance(n.name, zexus_ast.IndexExpression):
900
+ left = n.name.left
901
+ if isinstance(left, zexus_ast.Identifier):
902
+ assigned.add(left.value)
903
+
904
+ for attr in dir(n):
905
+ if attr.startswith("_"):
906
+ continue
907
+ try:
908
+ val = getattr(n, attr)
909
+ except Exception:
910
+ continue
911
+ if isinstance(val, list):
912
+ for item in val:
913
+ if hasattr(item, "__dict__"):
914
+ _walk(item)
915
+ elif hasattr(val, "__dict__"):
916
+ _walk(val)
917
+
918
+ _walk(node)
919
+ return assigned
920
+
921
+ # --- Safety analysis -------------------------------------------------
922
+
923
+ def _is_vm_safe_loop(self, body_node) -> bool:
924
+ """Heuristic: disallow VM only for transaction statements that mutate control flow.
925
+ Contract method calls are now handled via post-call state sync.
926
+ """
927
+ try:
928
+ from .. import zexus_ast
929
+ except Exception:
930
+ return False
931
+
932
+ unsafe_nodes = (
933
+ zexus_ast.TxStatement,
934
+ zexus_ast.BreakStatement,
935
+ zexus_ast.ContinueStatement,
936
+ )
937
+
938
+ def _walk(node) -> bool:
939
+ if node is None:
940
+ return True
941
+ if isinstance(node, unsafe_nodes):
942
+ return False
943
+ for attr in dir(node):
944
+ if attr.startswith('_'):
945
+ continue
946
+ try:
947
+ val = getattr(node, attr)
948
+ except Exception:
949
+ continue
950
+ if isinstance(val, list):
951
+ for item in val:
952
+ if not _walk(item):
953
+ return False
954
+ elif hasattr(val, '__dict__'):
955
+ if not _walk(val):
956
+ return False
957
+ return True
958
+
959
+ return _walk(body_node)
461
960
 
462
961
  def get_statistics(self) -> Dict:
463
962
  """Get comprehensive execution statistics"""
@@ -467,6 +966,8 @@ class UnifiedExecutor:
467
966
  "compiled_loops": len(self.compiled_loops),
468
967
  "compiled_functions": len(self.compiled_functions),
469
968
  "failed_compilations": len(self.failed_compilations),
969
+ "compile_errors": {str(k): v for k, v in self._compile_errors.items()},
970
+ "compile_info": {str(k): v for k, v in self._compile_info.items()},
470
971
  "vm_enabled": self.vm_enabled,
471
972
  "vm_initialized": self.vm is not None
472
973
  }