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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3411 -573
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +7 -4
- package/src/zexus.egg-info/SOURCES.txt +116 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- 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 =
|
|
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
|
|
63
|
+
Track loop iteration and determine optimal execution strategy.
|
|
52
64
|
|
|
53
65
|
Returns:
|
|
54
66
|
{
|
|
55
|
-
"should_compile": bool,
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
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
|
-
#
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
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
|
-
|
|
776
|
+
vm_target = vm or self.vm
|
|
777
|
+
if not vm_target or not env:
|
|
402
778
|
return
|
|
403
779
|
|
|
404
|
-
|
|
405
|
-
|
|
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
|
}
|