zexus 1.8.2 → 1.8.3

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 (37) hide show
  1. package/README.md +89 -64
  2. package/package.json +1 -1
  3. package/rust_core/Cargo.lock +1 -1
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/builtin_modules.py +50 -13
  6. package/src/zexus/cli/main.py +46 -1
  7. package/src/zexus/cli/zpm.py +1 -1
  8. package/src/zexus/evaluator/bytecode_compiler.py +11 -2
  9. package/src/zexus/evaluator/core.py +4 -1
  10. package/src/zexus/evaluator/expressions.py +11 -2
  11. package/src/zexus/evaluator/functions.py +72 -0
  12. package/src/zexus/evaluator/resource_limiter.py +1 -1
  13. package/src/zexus/evaluator/statements.py +44 -4
  14. package/src/zexus/kernel/__init__.py +34 -0
  15. package/src/zexus/kernel/hooks.py +276 -0
  16. package/src/zexus/kernel/registry.py +203 -0
  17. package/src/zexus/kernel/zir/__init__.py +145 -0
  18. package/src/zexus/lexer.py +7 -0
  19. package/src/zexus/object.py +28 -5
  20. package/src/zexus/parser/parser.py +53 -11
  21. package/src/zexus/parser/strategy_context.py +179 -10
  22. package/src/zexus/security.py +26 -2
  23. package/src/zexus/stdlib/blockchain.py +84 -0
  24. package/src/zexus/stdlib/http_server.py +2 -2
  25. package/src/zexus/stdlib/math.py +25 -17
  26. package/src/zexus/stdlib_integration.py +119 -2
  27. package/src/zexus/type_checker.py +17 -12
  28. package/src/zexus/vm/compiler.py +57 -6
  29. package/src/zexus/vm/fastops.c +4704 -1263
  30. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  31. package/src/zexus/vm/fastops.pyx +81 -3
  32. package/src/zexus/vm/optimizer.py +65 -27
  33. package/src/zexus/vm/vm.py +871 -98
  34. package/src/zexus/zexus_ast.py +4 -1
  35. package/src/zexus/zpm/package_manager.py +1 -1
  36. package/src/zexus.egg-info/PKG-INFO +90 -65
  37. package/src/zexus.egg-info/SOURCES.txt +51 -0
@@ -19,6 +19,7 @@ import threading
19
19
  import importlib
20
20
  import hashlib
21
21
  import types
22
+ import warnings
22
23
  from typing import List, Any, Dict, Tuple, Optional, Union, Callable
23
24
  from enum import Enum
24
25
 
@@ -34,6 +35,36 @@ from ..object import (
34
35
  DateTime as ZDateTime,
35
36
  )
36
37
 
38
+ # ==================== VM Warning / Diagnostics ====================
39
+
40
+ # Controlled by ZEXUS_VM_WARNINGS env var: "all", "errors", "none" (default: "errors")
41
+ _VM_WARN_LEVEL = os.environ.get("ZEXUS_VM_WARNINGS", "errors").lower()
42
+
43
+ def _vm_warn(category: str, message: str, exc: Exception = None):
44
+ """Emit a VM diagnostic warning instead of silently swallowing errors.
45
+
46
+ When ZEXUS_VM_WARNINGS=all → print all warnings to stderr
47
+ When ZEXUS_VM_WARNINGS=errors → only print on error (default)
48
+ When ZEXUS_VM_WARNINGS=none → fully silent (legacy behaviour)
49
+ """
50
+ if _VM_WARN_LEVEL == "none":
51
+ return
52
+ if _VM_WARN_LEVEL == "all" or (exc is not None and _VM_WARN_LEVEL == "errors"):
53
+ detail = f": {exc}" if exc else ""
54
+ print(f"[VM WARN] {category}{detail} — {message}", file=sys.stderr)
55
+
56
+
57
+ class VMRuntimeError(Exception):
58
+ """Proper Python exception for VM runtime errors (require/verify failures, etc.).
59
+
60
+ ZEvaluationError from object.py inherits from Object, not Exception,
61
+ so it cannot be used with Python's raise statement.
62
+ """
63
+ def __init__(self, message):
64
+ super().__init__(message)
65
+ self.zexus_message = message
66
+
67
+
37
68
  # ==================== Backend / Optional Imports ====================
38
69
 
39
70
  # JIT Compiler
@@ -148,6 +179,257 @@ except Exception:
148
179
  # Sentinel returned when Rust VM signals needs_fallback
149
180
  _RUST_VM_FALLBACK_SENTINEL = object()
150
181
 
182
+ # ==================== Zexus Method Dispatch Tables ====================
183
+ # These provide Zexus-specific methods on plain Python dicts/lists/strings
184
+ # that the VM creates via BUILD_MAP/BUILD_LIST/LOAD_CONST.
185
+
186
+ def _dict_has(target, args):
187
+ if not args:
188
+ return False
189
+ key = args[0]
190
+ return key in target
191
+
192
+ def _dict_keys(target, args):
193
+ return list(target.keys())
194
+
195
+ def _dict_values(target, args):
196
+ return list(target.values())
197
+
198
+ def _dict_entries(target, args):
199
+ return [[k, v] for k, v in target.items()]
200
+
201
+ def _dict_size(target, args):
202
+ return len(target)
203
+
204
+ def _dict_delete(target, args):
205
+ if args and args[0] in target:
206
+ del target[args[0]]
207
+ return True
208
+ return False
209
+
210
+ def _dict_contains(target, args):
211
+ if not args:
212
+ return False
213
+ return args[0] in target
214
+
215
+ _DICT_METHODS = {
216
+ "has": _dict_has,
217
+ "keys": _dict_keys,
218
+ "values": _dict_values,
219
+ "entries": _dict_entries,
220
+ "size": _dict_size,
221
+ "length": _dict_size,
222
+ "count": _dict_size,
223
+ "delete": _dict_delete,
224
+ "remove": _dict_delete,
225
+ "contains": _dict_contains,
226
+ }
227
+
228
+
229
+ def _list_push(target, args, vm=None):
230
+ if args:
231
+ target.append(args[0])
232
+ return target
233
+
234
+ def _list_pop(target, args, vm=None):
235
+ if target:
236
+ return target.pop()
237
+ return None
238
+
239
+ def _list_count(target, args, vm=None):
240
+ if args:
241
+ return target.count(args[0])
242
+ return len(target)
243
+
244
+ def _list_length(target, args, vm=None):
245
+ return len(target)
246
+
247
+ def _list_contains(target, args, vm=None):
248
+ if not args:
249
+ return False
250
+ val = args[0]
251
+ # Try direct comparison, then unwrap
252
+ if val in target:
253
+ return True
254
+ if hasattr(val, 'value'):
255
+ return val.value in target
256
+ return False
257
+
258
+ def _list_is_empty(target, args, vm=None):
259
+ return len(target) == 0
260
+
261
+ def _list_first(target, args, vm=None):
262
+ return target[0] if target else None
263
+
264
+ def _list_last(target, args, vm=None):
265
+ return target[-1] if target else None
266
+
267
+ def _list_reverse(target, args, vm=None):
268
+ return list(reversed(target))
269
+
270
+ def _list_sort(target, args, vm=None):
271
+ try:
272
+ return sorted(target)
273
+ except TypeError:
274
+ return target
275
+
276
+ def _list_join(target, args, vm=None):
277
+ sep = args[0] if args else ""
278
+ return sep.join(str(x) for x in target)
279
+
280
+ def _list_indexOf(target, args, vm=None):
281
+ if not args:
282
+ return -1
283
+ try:
284
+ return target.index(args[0])
285
+ except ValueError:
286
+ return -1
287
+
288
+ def _list_includes(target, args, vm=None):
289
+ return _list_contains(target, args, vm)
290
+
291
+ def _list_slice(target, args, vm=None):
292
+ start = args[0] if len(args) > 0 else 0
293
+ end = args[1] if len(args) > 1 else len(target)
294
+ return target[start:end]
295
+
296
+ def _list_flatten(target, args, vm=None):
297
+ result = []
298
+ for item in target:
299
+ if isinstance(item, list):
300
+ result.extend(item)
301
+ else:
302
+ result.append(item)
303
+ return result
304
+
305
+ def _list_map(target, args, vm=None):
306
+ if not args or not vm:
307
+ return target
308
+ fn = args[0]
309
+ return [vm._invoke_callable_sync(fn, [item]) for item in target]
310
+
311
+ def _list_filter(target, args, vm=None):
312
+ if not args or not vm:
313
+ return target
314
+ fn = args[0]
315
+ return [item for item in target if vm._invoke_callable_sync(fn, [item])]
316
+
317
+ def _list_reduce(target, args, vm=None):
318
+ if not args or not vm:
319
+ return None
320
+ fn = args[0]
321
+ acc = args[1] if len(args) > 1 else (target[0] if target else None)
322
+ items = target if len(args) > 1 else target[1:]
323
+ for item in items:
324
+ acc = vm._invoke_callable_sync(fn, [acc, item])
325
+ return acc
326
+
327
+ _LIST_METHODS = {
328
+ "push": _list_push,
329
+ "append": _list_push,
330
+ "pop": _list_pop,
331
+ "count": _list_count,
332
+ "length": _list_length,
333
+ "size": _list_length,
334
+ "contains": _list_contains,
335
+ "includes": _list_includes,
336
+ "is_empty": _list_is_empty,
337
+ "isEmpty": _list_is_empty,
338
+ "first": _list_first,
339
+ "last": _list_last,
340
+ "reverse": _list_reverse,
341
+ "sort": _list_sort,
342
+ "join": _list_join,
343
+ "indexOf": _list_indexOf,
344
+ "index_of": _list_indexOf,
345
+ "slice": _list_slice,
346
+ "flatten": _list_flatten,
347
+ "map": _list_map,
348
+ "filter": _list_filter,
349
+ "reduce": _list_reduce,
350
+ }
351
+
352
+
353
+ def _str_contains(target, args):
354
+ return args[0] in target if args else False
355
+
356
+ def _str_startsWith(target, args):
357
+ return target.startswith(args[0]) if args else False
358
+
359
+ def _str_endsWith(target, args):
360
+ return target.endswith(args[0]) if args else False
361
+
362
+ def _str_toUpperCase(target, args):
363
+ return target.upper()
364
+
365
+ def _str_toLowerCase(target, args):
366
+ return target.lower()
367
+
368
+ def _str_trim(target, args):
369
+ return target.strip()
370
+
371
+ def _str_split(target, args):
372
+ sep = args[0] if args else " "
373
+ return target.split(sep)
374
+
375
+ def _str_indexOf(target, args):
376
+ if not args:
377
+ return -1
378
+ return target.find(args[0])
379
+
380
+ def _str_length(target, args):
381
+ return len(target)
382
+
383
+ def _str_replace(target, args):
384
+ if len(args) >= 2:
385
+ return target.replace(args[0], args[1])
386
+ return target
387
+
388
+ def _str_substring(target, args):
389
+ start = args[0] if len(args) > 0 else 0
390
+ end = args[1] if len(args) > 1 else len(target)
391
+ return target[start:end]
392
+
393
+ def _str_charAt(target, args):
394
+ if args and 0 <= args[0] < len(target):
395
+ return target[args[0]]
396
+ return ""
397
+
398
+ def _str_repeat(target, args):
399
+ n = args[0] if args else 1
400
+ return target * int(n)
401
+
402
+ _STR_METHODS = {
403
+ "contains": _str_contains,
404
+ "includes": _str_contains,
405
+ "startsWith": _str_startsWith,
406
+ "starts_with": _str_startsWith,
407
+ "endsWith": _str_endsWith,
408
+ "ends_with": _str_endsWith,
409
+ "toUpperCase": _str_toUpperCase,
410
+ "to_upper": _str_toUpperCase,
411
+ "upper": _str_toUpperCase,
412
+ "toLowerCase": _str_toLowerCase,
413
+ "to_lower": _str_toLowerCase,
414
+ "lower": _str_toLowerCase,
415
+ "trim": _str_trim,
416
+ "strip": _str_trim,
417
+ "split": _str_split,
418
+ "indexOf": _str_indexOf,
419
+ "index_of": _str_indexOf,
420
+ "length": _str_length,
421
+ "size": _str_length,
422
+ "replace": _str_replace,
423
+ "substring": _str_substring,
424
+ "slice": _str_substring,
425
+ "charAt": _str_charAt,
426
+ "char_at": _str_charAt,
427
+ "repeat": _str_repeat,
428
+ }
429
+
430
+ # Sentinel for _vm_native_call: returned when no native handler exists
431
+ _VM_NATIVE_MISS = object()
432
+
151
433
  # Async Optimizer (Phase 8)
152
434
  try:
153
435
  from .async_optimizer import AsyncOptimizer, AsyncOptimizationLevel
@@ -329,9 +611,11 @@ def _fallback_type(args):
329
611
 
330
612
  _FALLBACK_BUILTINS = {
331
613
  "string": _fallback_string,
614
+ "str": _fallback_string, # alias — users expect str()
332
615
  "int": _fallback_int,
333
616
  "float": _fallback_float,
334
617
  "len": _fallback_len,
618
+ "length": _fallback_len, # alias — users expect length()
335
619
  "type": _fallback_type,
336
620
  }
337
621
 
@@ -365,8 +649,8 @@ class VM:
365
649
  enable_memory_pool: bool = True,
366
650
  pool_max_size: int = 1000,
367
651
  enable_peephole_optimizer: bool = True,
368
- enable_bytecode_optimizer: bool = False,
369
- optimizer_level: int = 2,
652
+ enable_bytecode_optimizer: bool = True,
653
+ optimizer_level: int = 1,
370
654
  optimization_level: str = "MODERATE",
371
655
  enable_async_optimizer: bool = True,
372
656
  async_optimization_level: str = "MODERATE",
@@ -441,6 +725,17 @@ class VM:
441
725
  self._call_depth = 0
442
726
  self._MAX_CALL_DEPTH = 256
443
727
 
728
+ # --- High-traffic hardening ---
729
+ # Stack overflow protection: limit operand stack depth
730
+ self._MAX_STACK_DEPTH = int(os.environ.get("ZEXUS_VM_MAX_STACK", "50000"))
731
+ # Execution timeout (seconds, 0 = unlimited)
732
+ self._exec_timeout = float(os.environ.get("ZEXUS_VM_TIMEOUT", "0"))
733
+ self._exec_start_time = 0.0
734
+ # VM pool ceiling (prevents unbounded memory growth under load)
735
+ self._VM_POOL_MAX = int(os.environ.get("ZEXUS_VM_POOL_MAX", "500"))
736
+ # Opcode execution hard-cap (prevents runaway programs; 0 = gas limit only)
737
+ self._MAX_OPCODES = int(os.environ.get("ZEXUS_VM_MAX_OPCODES", "0"))
738
+
444
739
  # --- Rust VM Adaptive Routing (Phase 3 + Phase 6) ---
445
740
  self._rust_vm_available = _RUST_VM_AVAILABLE
446
741
  self._rust_vm_executor = _RustVMExecutor() if _RUST_VM_AVAILABLE else None
@@ -670,7 +965,8 @@ class VM:
670
965
  def _return_vm_to_pool(self, vm) -> None:
671
966
  """Return a child VM to the pool for reuse."""
672
967
  if hasattr(self, "_vm_pool") and self._vm_pool is not None:
673
- if len(self._vm_pool) < 1000:
968
+ pool_max = getattr(self, '_VM_POOL_MAX', 500)
969
+ if len(self._vm_pool) < pool_max:
674
970
  # MEDIUM (M14): Scrub references that could leak state across pooled VMs.
675
971
  try:
676
972
  vm.env = None
@@ -1483,14 +1779,14 @@ class VM:
1483
1779
  op_name = op.name if hasattr(op, "name") else op
1484
1780
  normalized_for_opt.append((str(op_name), operand))
1485
1781
  instrs = self.bytecode_optimizer.optimize(normalized_for_opt, consts)
1486
- except Exception:
1487
- pass
1782
+ except Exception as _e:
1783
+ _vm_warn("OPTIMIZER", "Bytecode optimizer pass failed (compile path)", _e)
1488
1784
 
1489
1785
  if self.enable_peephole_optimizer and self.peephole_optimizer:
1490
1786
  try:
1491
1787
  instrs, consts = self.peephole_optimizer.optimize_bytecode(instrs, consts)
1492
- except Exception:
1493
- pass
1788
+ except Exception as _e:
1789
+ _vm_warn("OPTIMIZER", "Peephole optimizer pass failed (compile path)", _e)
1494
1790
 
1495
1791
  normalized: List[Tuple[Any, Any]] = []
1496
1792
  for instr in instrs:
@@ -1509,8 +1805,8 @@ class VM:
1509
1805
  ssa_program = self.ssa_converter.convert_to_ssa(instrs)
1510
1806
  ssa_instrs = destruct_ssa(ssa_program)
1511
1807
  instrs, consts = self._normalize_ssa_instructions(ssa_instrs, consts)
1512
- except Exception:
1513
- pass
1808
+ except Exception as _e:
1809
+ _vm_warn("OPTIMIZER", "SSA conversion pass failed (compile path)", _e)
1514
1810
 
1515
1811
  mapped: List[Tuple[Any, Any]] = []
1516
1812
  for op, operand in instrs:
@@ -1995,8 +2291,8 @@ class VM:
1995
2291
  return _fastops.execute(instrs, consts, self.env, self.builtins, self._closure_cells)
1996
2292
  except NotImplementedError:
1997
2293
  pass
1998
- except Exception:
1999
- pass
2294
+ except Exception as _e:
2295
+ _vm_warn("FASTOPS", "Cython fast-path execution failed, falling back to interpreter", _e)
2000
2296
 
2001
2297
  # Rust VM adaptive routing (Phase 3) — delegate to Rust when
2002
2298
  # the program is large enough to amortise serialisation overhead.
@@ -2067,7 +2363,16 @@ class VM:
2067
2363
  _gas_light_cost = self.gas_light_cost if _gas_light else 0
2068
2364
  _gas_consume_light = _gas.consume_light if _gas else None
2069
2365
 
2366
+ # --- High-traffic hardening caches ---
2367
+ _exec_timeout = self._exec_timeout
2368
+ _max_stack = self._MAX_STACK_DEPTH
2369
+ _max_opcodes = self._MAX_OPCODES
2370
+ _total_ops = 0
2371
+ if _exec_timeout > 0:
2372
+ self._exec_start_time = time.monotonic()
2373
+
2070
2374
  while ip < instr_count:
2375
+ try:
2071
2376
  op_name, operand = instrs[ip]
2072
2377
  ip += 1
2073
2378
 
@@ -2082,6 +2387,25 @@ class VM:
2082
2387
  from .gas_metering import OutOfGasError
2083
2388
  raise OutOfGasError(_gas.gas_used, _gas.gas_limit, op_name)
2084
2389
 
2390
+ # --- Hardening checks (every 4096 ops to amortize cost) ---
2391
+ _total_ops += 1
2392
+ if (_total_ops & 0xFFF) == 0:
2393
+ if _max_stack and len(stack) > _max_stack:
2394
+ raise VMRuntimeError(
2395
+ f"Stack overflow: depth {len(stack)} exceeds limit {_max_stack}. "
2396
+ "Possible infinite recursion or deeply nested expression."
2397
+ )
2398
+ if _exec_timeout > 0 and (time.monotonic() - self._exec_start_time) > _exec_timeout:
2399
+ raise VMRuntimeError(
2400
+ f"Execution timeout: exceeded {_exec_timeout}s limit. "
2401
+ "Set ZEXUS_VM_TIMEOUT=0 to disable."
2402
+ )
2403
+ if _max_opcodes > 0 and _total_ops > _max_opcodes:
2404
+ raise VMRuntimeError(
2405
+ f"Opcode limit exceeded: {_total_ops} > {_max_opcodes}. "
2406
+ "Set ZEXUS_VM_MAX_OPCODES=0 to disable."
2407
+ )
2408
+
2085
2409
  if trace_interval > 0:
2086
2410
  trace_counter += 1
2087
2411
  if trace_counter % trace_interval == 0:
@@ -2144,7 +2468,13 @@ class VM:
2144
2468
  a = stack_pop() if stack else 0
2145
2469
  if hasattr(a, 'value'): a = a.value
2146
2470
  if hasattr(b, 'value'): b = b.value
2147
- stack_append(a / b if b != 0 else 0)
2471
+ if b == 0:
2472
+ raise VMRuntimeError("Division by zero")
2473
+ if isinstance(a, int) and isinstance(b, int) and not isinstance(a, bool) and not isinstance(b, bool):
2474
+ result_val = a // b if a % b == 0 else a / b
2475
+ else:
2476
+ result_val = a / b
2477
+ stack_append(result_val)
2148
2478
  elif op_name == "MOD":
2149
2479
  b = stack_pop() if stack else 1
2150
2480
  a = stack_pop() if stack else 0
@@ -2231,8 +2561,8 @@ class VM:
2231
2561
  mod_name = const(operand)
2232
2562
  alias, names, is_named = "", [], False
2233
2563
  self._execute_import(mod_name, alias=alias or "", names=names, is_named=bool(is_named))
2234
- except Exception:
2235
- pass
2564
+ except Exception as _e:
2565
+ _vm_warn("IMPORT", f"Import of '{mod_name}' failed silently", _e)
2236
2566
  elif op_name == "EXPORT":
2237
2567
  # vm/compiler.py emits: LOAD_NAME <name_idx>; EXPORT <name_idx>
2238
2568
  try:
@@ -2252,13 +2582,13 @@ class VM:
2252
2582
  if callable(export_fn):
2253
2583
  try:
2254
2584
  export_fn(name, value)
2255
- except Exception:
2256
- pass
2585
+ except Exception as _e:
2586
+ _vm_warn("EXPORT", f"export_fn('{name}') call failed", _e)
2257
2587
  else:
2258
2588
  self.env[name] = value
2259
2589
  self._bump_env_version(name, value)
2260
- except Exception:
2261
- pass
2590
+ except Exception as _e:
2591
+ _vm_warn("EXPORT", f"Export of '{name}' failed silently", _e)
2262
2592
  elif op_name == "RETURN":
2263
2593
  return stack_pop() if stack else None
2264
2594
  elif op_name == "BUILD_LIST":
@@ -2267,11 +2597,15 @@ class VM:
2267
2597
  stack_append(elements)
2268
2598
  elif op_name == "BUILD_MAP":
2269
2599
  count = operand if operand is not None else 0
2270
- result = {}
2600
+ pairs = []
2271
2601
  for _ in range(count):
2272
2602
  val = stack_pop()
2273
2603
  key = stack_pop()
2274
- result[key] = val
2604
+ pairs.append((key, val))
2605
+ pairs.reverse()
2606
+ result = {}
2607
+ for k, v in pairs:
2608
+ result[k] = v
2275
2609
  stack_append(result)
2276
2610
  elif op_name == "INDEX":
2277
2611
  idx = stack_pop()
@@ -2281,10 +2615,78 @@ class VM:
2281
2615
  stack_append(obj.get(idx))
2282
2616
  elif isinstance(obj, ZMap):
2283
2617
  stack_append(obj.get(idx))
2618
+ elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
2619
+ # EntityInstance — access fields via .get() or .data dict
2620
+ idx_str = idx.value if hasattr(idx, 'value') else str(idx) if idx is not None else None
2621
+ val = obj.get(idx_str) if idx_str else None
2622
+ stack_append(self._unwrap_after_builtin(val) if val is not None else None)
2623
+ elif isinstance(obj, dict) and isinstance(idx, int) and not isinstance(idx, bool):
2624
+ keys = list(obj.keys())
2625
+ if 0 <= idx < len(keys):
2626
+ stack_append(obj[keys[idx]])
2627
+ else:
2628
+ stack_append(None)
2284
2629
  else:
2285
2630
  stack_append(obj[idx] if obj is not None else None)
2286
2631
  except (IndexError, KeyError, TypeError):
2287
2632
  stack_append(None)
2633
+ elif op_name == "FOR_ITER":
2634
+ # FOR_ITER var_count: pop counter, pop iterable, push values
2635
+ # var_count=1: single variable (element for list, key for dict)
2636
+ # var_count=2: two variables (index/key + element/value)
2637
+ var_count = operand if operand else 1
2638
+ idx = stack_pop() if stack else 0
2639
+ obj = stack_pop() if stack else None
2640
+ try:
2641
+ if isinstance(obj, dict):
2642
+ keys = list(obj.keys())
2643
+ if isinstance(idx, int) and 0 <= idx < len(keys):
2644
+ key = keys[idx]
2645
+ if var_count == 2:
2646
+ stack_append(key) # key (below)
2647
+ stack_append(obj[key]) # value (top)
2648
+ else:
2649
+ stack_append(key) # single var = key
2650
+ else:
2651
+ for _ in range(var_count):
2652
+ stack_append(None)
2653
+ elif isinstance(obj, ZMap):
2654
+ keys = list(obj.pairs.keys())
2655
+ if isinstance(idx, int) and 0 <= idx < len(keys):
2656
+ key = keys[idx]
2657
+ if var_count == 2:
2658
+ stack_append(key)
2659
+ stack_append(obj.pairs[key])
2660
+ else:
2661
+ stack_append(key)
2662
+ else:
2663
+ for _ in range(var_count):
2664
+ stack_append(None)
2665
+ elif isinstance(obj, (list, ZList)):
2666
+ elems = obj.elements if isinstance(obj, ZList) else obj
2667
+ if isinstance(idx, int) and 0 <= idx < len(elems):
2668
+ if var_count == 2:
2669
+ stack_append(idx) # index (below)
2670
+ stack_append(elems[idx]) # element (top)
2671
+ else:
2672
+ stack_append(elems[idx]) # single var = element
2673
+ else:
2674
+ for _ in range(var_count):
2675
+ stack_append(None)
2676
+ else:
2677
+ # Fallback: treat as indexable
2678
+ try:
2679
+ if var_count == 2:
2680
+ stack_append(idx)
2681
+ stack_append(obj[idx] if obj is not None else None)
2682
+ else:
2683
+ stack_append(obj[idx] if obj is not None else None)
2684
+ except Exception:
2685
+ for _ in range(var_count):
2686
+ stack_append(None)
2687
+ except Exception:
2688
+ for _ in range(var_count):
2689
+ stack_append(None)
2288
2690
  elif op_name == "SLICE":
2289
2691
  end = stack_pop() if stack else None
2290
2692
  start = stack_pop() if stack else None
@@ -2321,6 +2723,11 @@ class VM:
2321
2723
  name_idx, arg_count = operand
2322
2724
  func_name = const(name_idx)
2323
2725
  args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
2726
+ # --- Fast-path: native VM builtins for common list/type ops ---
2727
+ res = self._vm_native_call(func_name, args)
2728
+ if res is not _VM_NATIVE_MISS:
2729
+ stack_append(res)
2730
+ continue
2324
2731
  fn = resolve(func_name) or builtins.get(func_name)
2325
2732
  if fn is None:
2326
2733
  res = self._call_fallback_builtin(func_name, args)
@@ -2369,7 +2776,7 @@ class VM:
2369
2776
  norm_key = str(key)
2370
2777
  existing = target.pairs.get(norm_key)
2371
2778
  if existing is not None and existing.__class__.__name__ == 'SealedObject':
2372
- raise ZEvaluationError(f"Cannot modify sealed map key: {key}")
2779
+ raise VMRuntimeError(f"Cannot modify sealed map key: {key}")
2373
2780
  target.pairs[norm_key] = args[1]
2374
2781
  result = args[1]
2375
2782
  elif isinstance(target, ZList) and len(args) >= 2:
@@ -2383,12 +2790,22 @@ class VM:
2383
2790
  result = target.get(args[0])
2384
2791
  elif isinstance(target, dict) and args:
2385
2792
  result = target.get(args[0])
2793
+ # ── Zexus dict/map methods ──
2794
+ elif isinstance(target, dict) and method_name in _DICT_METHODS:
2795
+ result = _DICT_METHODS[method_name](target, args)
2796
+ # ── Zexus list methods ──
2797
+ elif isinstance(target, list) and method_name in _LIST_METHODS:
2798
+ result = _LIST_METHODS[method_name](target, args, self)
2799
+ # ── Zexus string methods ──
2800
+ elif isinstance(target, str) and method_name in _STR_METHODS:
2801
+ result = _STR_METHODS[method_name](target, args)
2386
2802
  elif hasattr(target, "call_method"):
2387
2803
  wrapped_args = [self._wrap_for_builtin(arg) for arg in args]
2388
2804
  try:
2389
2805
  from .. import security as _security
2390
2806
  _security._set_vm_action_context(True)
2391
- except Exception:
2807
+ except Exception as _e:
2808
+ _vm_warn("SECURITY", "Failed to set VM action context (pre-call)", _e)
2392
2809
  _security = None
2393
2810
  try:
2394
2811
  result = target.call_method(method_name, wrapped_args)
@@ -2396,8 +2813,8 @@ class VM:
2396
2813
  if _security is not None:
2397
2814
  try:
2398
2815
  _security._set_vm_action_context(False)
2399
- except Exception:
2400
- pass
2816
+ except Exception as _e:
2817
+ _vm_warn("SECURITY", "Failed to clear VM action context (post-call)", _e)
2401
2818
  else:
2402
2819
  attr = self._get_cached_method(target, method_name)
2403
2820
  if callable(attr):
@@ -2417,7 +2834,8 @@ class VM:
2417
2834
  result = candidate
2418
2835
  else:
2419
2836
  result = attr
2420
- except Exception:
2837
+ except Exception as _e:
2838
+ _vm_warn("CALL_METHOD", f"Method '{method_name}' call failed on {type(target).__name__}", _e)
2421
2839
  result = None
2422
2840
  stack_append(self._unwrap_after_builtin(result))
2423
2841
  elif op_name == "PRINT":
@@ -2438,17 +2856,25 @@ class VM:
2438
2856
  stack_append(obj.get(key))
2439
2857
  elif isinstance(obj, dict):
2440
2858
  stack_append(obj.get(attr_name))
2859
+ elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
2860
+ # EntityInstance — access fields via .data dict
2861
+ stack_append(obj.data.get(attr_name, getattr(obj, attr_name, None)))
2441
2862
  elif hasattr(obj, 'get') and hasattr(obj, 'set') and callable(getattr(obj, 'get', None)):
2442
2863
  # Contract-like objects (e.g., SmartContract) expose state via get/set.
2443
2864
  stack_append(obj.get(attr_name))
2444
2865
  else:
2445
2866
  stack_append(getattr(obj, attr_name, None))
2446
- except Exception:
2867
+ except Exception as _e:
2868
+ _vm_warn("GET_ATTR", f"Attribute '{attr_name}' access failed on {type(obj).__name__}", _e)
2447
2869
  stack_append(None)
2448
2870
  elif op_name == "DEFINE_CONTRACT":
2449
2871
  contract_obj = self._build_smart_contract(operand, stack, stack_pop, const, env)
2450
2872
  stack_append(contract_obj)
2451
2873
 
2874
+ elif op_name == "DEFINE_ENTITY":
2875
+ entity_obj = self._build_entity_definition(operand, stack, stack_pop, const)
2876
+ stack_append(entity_obj)
2877
+
2452
2878
  # --- Blockchain / TX opcodes (sync-safe) ---
2453
2879
 
2454
2880
  elif op_name == "HASH_BLOCK":
@@ -2585,7 +3011,7 @@ class VM:
2585
3011
  if env.get("_in_transaction", False):
2586
3012
  env["_blockchain_state"] = dict(env.get("_tx_snapshot", {}))
2587
3013
  env["_in_transaction"] = False
2588
- raise ZEvaluationError(
3014
+ raise VMRuntimeError(
2589
3015
  f"Out of gas: required {amount}, remaining {current}")
2590
3016
  env["_gas_remaining"] = new_gas
2591
3017
 
@@ -2600,7 +3026,7 @@ class VM:
2600
3026
  env["_in_transaction"] = False
2601
3027
  env["_tx_pending_state"] = {}
2602
3028
  env.pop("_tx_snapshot", None)
2603
- raise ZEvaluationError(f"Requirement failed: {message}")
3029
+ raise VMRuntimeError(f"Requirement failed: {message}")
2604
3030
 
2605
3031
  elif op_name == "LEDGER_APPEND":
2606
3032
  entry = stack_pop()
@@ -2628,7 +3054,7 @@ class VM:
2628
3054
  ip = handler_ip
2629
3055
  else:
2630
3056
  msg = exc.value if hasattr(exc, 'value') else exc
2631
- raise ZEvaluationError(str(msg))
3057
+ raise VMRuntimeError(str(msg))
2632
3058
 
2633
3059
  elif op_name == "ENABLE_ERROR_MODE":
2634
3060
  env["_continue_on_error"] = True
@@ -2669,11 +3095,11 @@ class VM:
2669
3095
  if owner is not None:
2670
3096
  ov = owner.value if hasattr(owner, 'value') else str(owner)
2671
3097
  if caller and caller != ov:
2672
- raise ZEvaluationError(f"Access denied: '{r_key}' restricted to owner only")
3098
+ raise VMRuntimeError(f"Access denied: '{r_key}' restricted to owner only")
2673
3099
  elif isinstance(rv, (list, tuple)):
2674
3100
  allowed = [a.value if hasattr(a, 'value') else str(a) for a in rv]
2675
3101
  if caller and caller not in allowed:
2676
- raise ZEvaluationError(f"Access denied: '{r_key}' restricted to allowed addresses")
3102
+ raise VMRuntimeError(f"Access denied: '{r_key}' restricted to allowed addresses")
2677
3103
 
2678
3104
  elif op_name == "SPAWN":
2679
3105
  # Sync SPAWN: schedule the function to run in a background thread
@@ -2720,6 +3146,19 @@ class VM:
2720
3146
  else:
2721
3147
  # Truly unknown op — fallback to async path
2722
3148
  return self._run_coroutine_sync(self._run_stack_bytecode(bytecode, debug))
3149
+ except Exception as _sync_exc:
3150
+ # Route Python exceptions through the Zexus try/catch stack
3151
+ _ts = env.get("_try_stack_sync", [])
3152
+ if _ts:
3153
+ _handler_ip = _ts.pop()
3154
+ _exc_msg = _sync_exc.args[0] if _sync_exc.args else str(_sync_exc)
3155
+ stack_append(str(_exc_msg))
3156
+ ip = _handler_ip
3157
+ continue
3158
+ elif env.get("_continue_on_error", False):
3159
+ env.setdefault("_errors", []).append(str(_sync_exc))
3160
+ else:
3161
+ raise
2723
3162
 
2724
3163
  return stack_pop() if stack else None
2725
3164
 
@@ -2827,6 +3266,93 @@ class VM:
2827
3266
 
2828
3267
  return contract
2829
3268
 
3269
+ def _build_entity_definition(self, operand, stack, stack_pop, const):
3270
+ """Create a real EntityDefinition from DEFINE_ENTITY bytecode.
3271
+
3272
+ This mirrors the interpreter's eval_entity_statement logic so that
3273
+ entity construction (``User("Alice", 30)``) works correctly on the VM.
3274
+
3275
+ Stack layout at DEFINE_ENTITY execution (top → bottom):
3276
+ prop_name_N, prop_default_N ← from compiler's property loop
3277
+ ...
3278
+ prop_name_1, prop_default_1
3279
+ entity_name
3280
+ """
3281
+ from ..object import EntityDefinition, Null as ObjNull
3282
+
3283
+ member_count = operand or 0
3284
+
3285
+ # Pop properties (name, default_value pairs) — pushed in order, so
3286
+ # we pop in reverse and then reverse again to restore ordering.
3287
+ raw_members = []
3288
+ for _ in range(member_count):
3289
+ key_obj = stack_pop() # property name string constant
3290
+ val_obj = stack_pop() # default value (or None / method)
3291
+ key_str = key_obj.value if hasattr(key_obj, 'value') else str(key_obj) if key_obj is not None else ""
3292
+ raw_members.append((key_str, val_obj))
3293
+ raw_members.reverse() # restore push order
3294
+
3295
+ # Pop entity name
3296
+ name_obj = stack_pop()
3297
+ entity_name = name_obj.value if hasattr(name_obj, 'value') else str(name_obj) if name_obj is not None else "UnknownEntity"
3298
+
3299
+ # Build properties dict in the format EntityDefinition expects:
3300
+ # { prop_name: { "type": "any", "default_value": <wrapped_value> } }
3301
+ properties = {}
3302
+ methods = {}
3303
+ ZAction_cls, ZLambda_cls = _get_action_types()
3304
+
3305
+ for key, val in raw_members:
3306
+ # If the value is an Action/Lambda, it's a method
3307
+ if ZAction_cls is not None and isinstance(val, (ZAction_cls, ZLambda_cls)):
3308
+ methods[key] = val
3309
+ elif isinstance(val, dict) and val.get("type") == "function":
3310
+ # Function descriptors compiled by the VM
3311
+ methods[key] = val
3312
+ else:
3313
+ wrapped = self._wrap_for_builtin(val) if val is not None else ObjNull()
3314
+ properties[key] = {"type": "any", "default_value": wrapped}
3315
+
3316
+ entity_def = EntityDefinition(entity_name, properties, methods)
3317
+ return entity_def
3318
+
3319
+ def _construct_entity(self, entity_def, args):
3320
+ """Construct an EntityInstance from an EntityDefinition + call args.
3321
+
3322
+ Mirrors the interpreter's apply_function logic for entity construction:
3323
+ - Single Map arg → use as field values
3324
+ - Positional args → map to property names in order
3325
+ """
3326
+ from ..object import EntityDefinition, Map as ObjMap, String as ObjString
3327
+
3328
+ values = {}
3329
+
3330
+ # If single arg is a Map or dict (handles Entity{field: value} syntax)
3331
+ if len(args) == 1 and isinstance(args[0], (ZMap, ObjMap, dict)):
3332
+ map_arg = args[0]
3333
+ if isinstance(map_arg, dict):
3334
+ pairs = map_arg
3335
+ else:
3336
+ pairs = map_arg.pairs if hasattr(map_arg, 'pairs') else {}
3337
+ for key, value in pairs.items():
3338
+ key_str = key.value if hasattr(key, 'value') else str(key)
3339
+ values[key_str] = self._wrap_for_builtin(value)
3340
+ else:
3341
+ # Positional args → map to property names
3342
+ if hasattr(entity_def, 'get_all_properties'):
3343
+ all_props = entity_def.get_all_properties()
3344
+ prop_names = list(all_props.keys())
3345
+ elif isinstance(entity_def.properties, dict):
3346
+ prop_names = list(entity_def.properties.keys())
3347
+ else:
3348
+ prop_names = [p.get('name', '') if isinstance(p, dict) else str(p) for p in (entity_def.properties or [])]
3349
+
3350
+ for i, arg in enumerate(args):
3351
+ if i < len(prop_names):
3352
+ values[prop_names[i]] = self._wrap_for_builtin(arg)
3353
+
3354
+ return entity_def.create_instance(values)
3355
+
2830
3356
  def _invoke_callable_sync(self, fn, args):
2831
3357
  """Synchronous callable invocation for fast dispatch."""
2832
3358
  if fn is None:
@@ -2847,6 +3373,11 @@ class VM:
2847
3373
 
2848
3374
  def _invoke_callable_sync_inner(self, fn, args):
2849
3375
  """Inner implementation of callable invocation."""
3376
+ # --- Entity construction ---
3377
+ from ..object import EntityDefinition as _EntityDef, EntityInstance as _EntityInst
3378
+ if isinstance(fn, _EntityDef):
3379
+ return self._construct_entity(fn, args)
3380
+
2850
3381
  real_fn = fn.fn if hasattr(fn, "fn") else fn
2851
3382
  ZAction, ZLambda = _get_action_types()
2852
3383
  if ZAction is not None and isinstance(real_fn, (ZAction, ZLambda)):
@@ -2861,7 +3392,8 @@ class VM:
2861
3392
  action_bytecode = compiler.compile(real_fn.body, optimize=True)
2862
3393
  if action_bytecode and not compiler.errors:
2863
3394
  real_fn._cached_bytecode = action_bytecode
2864
- except Exception:
3395
+ except Exception as _e:
3396
+ _vm_warn("COMPILE_ACTION", "Bytecode compilation failed for Action/Lambda — using interpreter fallback", _e)
2865
3397
  action_bytecode = None
2866
3398
 
2867
3399
  if action_bytecode:
@@ -2884,7 +3416,8 @@ class VM:
2884
3416
  call_args = [self._wrap_for_builtin(arg) for arg in args]
2885
3417
  result = self._action_evaluator.apply_function(real_fn, call_args)
2886
3418
  return self._unwrap_after_builtin(result)
2887
- except Exception:
3419
+ except Exception as _e:
3420
+ _vm_warn("CALL_ACTION_FALLBACK", "Action/Lambda interpreter fallback failed", _e)
2888
3421
  return None
2889
3422
  if callable(real_fn) and not _iscoroutinefunction(real_fn):
2890
3423
  try:
@@ -2892,13 +3425,14 @@ class VM:
2892
3425
  call_args = [self._wrap_for_builtin(arg) for arg in args] if wrap_args else list(args)
2893
3426
  result = real_fn(*call_args)
2894
3427
  return self._unwrap_after_builtin(result) if wrap_args else result
2895
- except Exception:
3428
+ except Exception as _e:
3429
+ _vm_warn("CALL_CALLABLE", f"Callable invocation failed: {getattr(real_fn, '__name__', repr(real_fn))}", _e)
2896
3430
  return None
2897
3431
  if isinstance(fn, dict):
2898
3432
  # Function descriptor - execute bytecode
2899
3433
  bytecode = fn.get("bytecode")
2900
3434
  if bytecode:
2901
- params = fn.get("parameters", [])
3435
+ params = fn.get("params") or fn.get("parameters") or []
2902
3436
  local_env = {}
2903
3437
  for i, p in enumerate(params):
2904
3438
  pname = p.get("name") if isinstance(p, dict) else str(p)
@@ -2939,15 +3473,15 @@ class VM:
2939
3473
  op_name = op.name if hasattr(op, "name") else op
2940
3474
  normalized_for_opt.append((str(op_name), operand))
2941
3475
  instrs = self.bytecode_optimizer.optimize(normalized_for_opt, consts)
2942
- except Exception:
2943
- pass
3476
+ except Exception as _e:
3477
+ _vm_warn("OPTIMIZER", "Bytecode optimizer pass failed (execute path)", _e)
2944
3478
 
2945
3479
  # Peephole optimization with constant pool awareness
2946
3480
  if not fast_single_shot and self.enable_peephole_optimizer and self.peephole_optimizer:
2947
3481
  try:
2948
3482
  instrs, consts = self.peephole_optimizer.optimize_bytecode(instrs, consts)
2949
- except Exception:
2950
- pass
3483
+ except Exception as _e:
3484
+ _vm_warn("OPTIMIZER", "Peephole optimizer pass failed (execute path)", _e)
2951
3485
 
2952
3486
  # Normalize opcodes to names for SSA pipeline and stack dispatch
2953
3487
  normalized_instrs: List[Tuple[Any, Any]] = []
@@ -2970,8 +3504,8 @@ class VM:
2970
3504
  ssa_program = self.ssa_converter.convert_to_ssa(instrs)
2971
3505
  ssa_instrs = destruct_ssa(ssa_program)
2972
3506
  instrs, consts = self._normalize_ssa_instructions(ssa_instrs, consts)
2973
- except Exception:
2974
- pass
3507
+ except Exception as _e:
3508
+ _vm_warn("OPTIMIZER", "SSA conversion pass failed (execute path)", _e)
2975
3509
 
2976
3510
  # 1. JIT Check (with thread safety)
2977
3511
  if self.use_jit and self.jit_compiler:
@@ -3292,6 +3826,11 @@ class VM:
3292
3826
  args.reverse()
3293
3827
  else:
3294
3828
  args = []
3829
+ # ── fast-path for native builtins (push/append/length/str/range) ──
3830
+ native_res = self._vm_native_call(func_name, args)
3831
+ if native_res is not _VM_NATIVE_MISS:
3832
+ stack_append(native_res)
3833
+ return
3295
3834
  fn = None
3296
3835
  try:
3297
3836
  fn = builtins_get(func_name)
@@ -3442,7 +3981,7 @@ class VM:
3442
3981
  norm_key = str(key)
3443
3982
  existing = target.pairs.get(norm_key)
3444
3983
  if existing is not None and existing.__class__.__name__ == 'SealedObject':
3445
- raise ZEvaluationError(f"Cannot modify sealed map key: {key}")
3984
+ raise VMRuntimeError(f"Cannot modify sealed map key: {key}")
3446
3985
  target.pairs[norm_key] = args[1]
3447
3986
  result = args[1]
3448
3987
  else:
@@ -3464,21 +4003,27 @@ class VM:
3464
4003
  result = target.get(args[0])
3465
4004
  elif isinstance(target, dict) and args:
3466
4005
  result = target.get(args[0])
4006
+ elif isinstance(target, dict) and method_name in _DICT_METHODS:
4007
+ result = _DICT_METHODS[method_name](target, args)
4008
+ elif isinstance(target, list) and method_name in _LIST_METHODS:
4009
+ result = _LIST_METHODS[method_name](target, args)
4010
+ elif isinstance(target, str) and method_name in _STR_METHODS:
4011
+ result = _STR_METHODS[method_name](target, args)
3467
4012
  elif hasattr(target, "call_method"):
3468
4013
  wrapped_args = [self._wrap_for_builtin(arg) for arg in args]
3469
4014
  if _cached_security is not None:
3470
4015
  try:
3471
4016
  _cached_security._set_vm_action_context(True)
3472
- except Exception:
3473
- pass
4017
+ except Exception as _e:
4018
+ _vm_warn("SECURITY", "Failed to set VM action context (async pre-call)", _e)
3474
4019
  try:
3475
4020
  result = target.call_method(method_name, wrapped_args)
3476
4021
  finally:
3477
4022
  if _cached_security is not None:
3478
4023
  try:
3479
4024
  _cached_security._set_vm_action_context(False)
3480
- except Exception:
3481
- pass
4025
+ except Exception as _e:
4026
+ _vm_warn("SECURITY", "Failed to clear VM action context (async post-call)", _e)
3482
4027
  else:
3483
4028
  attr = self._get_cached_method(target, method_name)
3484
4029
  if callable(attr):
@@ -3518,7 +4063,7 @@ class VM:
3518
4063
 
3519
4064
  def _op_load_const(idx):
3520
4065
  value = const(idx)
3521
- if self.integer_pool and isinstance(value, int):
4066
+ if self.integer_pool and isinstance(value, int) and not isinstance(value, bool):
3522
4067
  value = self.integer_pool.get(value)
3523
4068
  elif self.string_pool and isinstance(value, str):
3524
4069
  value = self.string_pool.get(value)
@@ -3642,11 +4187,15 @@ class VM:
3642
4187
 
3643
4188
  def _op_build_map(count):
3644
4189
  total = count if count is not None else 0
3645
- result = {}
4190
+ pairs = []
3646
4191
  for _ in range(total):
3647
4192
  val = stack_pop() if stack else None
3648
4193
  key = stack_pop() if stack else None
3649
- result[key] = val
4194
+ pairs.append((key, val))
4195
+ pairs.reverse()
4196
+ result = {}
4197
+ for k, v in pairs:
4198
+ result[k] = v
3650
4199
  stack_append(result)
3651
4200
 
3652
4201
  def _op_index(_):
@@ -3662,6 +4211,30 @@ class VM:
3662
4211
  stack.append(obj.get(key))
3663
4212
  elif isinstance(obj, ZString):
3664
4213
  stack.append(obj[idx])
4214
+ elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
4215
+ # EntityInstance field access
4216
+ idx_str = idx.value if hasattr(idx, 'value') else str(idx) if idx is not None else None
4217
+ if idx_str:
4218
+ val = obj.get(idx_str)
4219
+ # Unwrap Zexus objects to Python primitives for consistency
4220
+ if hasattr(val, 'value'):
4221
+ stack.append(val.value)
4222
+ else:
4223
+ stack.append(val)
4224
+ else:
4225
+ stack.append(None)
4226
+ elif isinstance(obj, dict):
4227
+ raw_idx = idx.value if hasattr(idx, 'value') else idx
4228
+ if raw_idx in obj:
4229
+ stack.append(obj[raw_idx])
4230
+ elif isinstance(raw_idx, int) and not isinstance(raw_idx, bool):
4231
+ keys = list(obj.keys())
4232
+ if 0 <= raw_idx < len(keys):
4233
+ stack.append(obj[keys[raw_idx]])
4234
+ else:
4235
+ stack.append(None)
4236
+ else:
4237
+ stack.append(None)
3665
4238
  else:
3666
4239
  # Fallback
3667
4240
  if obj is None:
@@ -3726,8 +4299,8 @@ class VM:
3726
4299
  env_dir = env_get("__DIR__", None)
3727
4300
  if env_dir:
3728
4301
  sandbox = _os.path.realpath(str(env_dir))
3729
- except Exception:
3730
- pass
4302
+ except Exception as _e:
4303
+ _vm_warn("SANDBOX", "Failed to resolve sandbox __DIR__ for READ", _e)
3731
4304
 
3732
4305
  if isinstance(path, str) and '\x00' not in path:
3733
4306
  candidate = path
@@ -3767,7 +4340,7 @@ class VM:
3767
4340
  "ADD": _op_add,
3768
4341
  "SUB": _binary_op(lambda a, b: a - b),
3769
4342
  "MUL": _binary_op(lambda a, b: a * b),
3770
- "DIV": _binary_op(lambda a, b: a / b if b != 0 else 0),
4343
+ "DIV": _binary_op(_vm_div),
3771
4344
  "MOD": _binary_op(lambda a, b: a % b if b != 0 else 0),
3772
4345
  "POW": _binary_op(lambda a, b: _safe_pow(a, b)),
3773
4346
  "NEG": _op_neg,
@@ -4273,29 +4846,74 @@ class VM:
4273
4846
  stack.append(ok)
4274
4847
 
4275
4848
  elif op_name == "FOR_ITER":
4276
- target = int(operand) if operand is not None else ip
4277
- it = stack.pop() if stack else None
4278
- if it is None:
4279
- ip = target
4280
- else:
4281
- try:
4282
- iterator = iter(it)
4283
- value = next(iterator)
4284
- stack.append(iterator)
4285
- stack.append(value)
4286
- except StopIteration:
4287
- ip = target
4849
+ var_count = operand if operand else 1
4850
+ idx = stack.pop() if stack else 0
4851
+ obj = stack.pop() if stack else None
4852
+ try:
4853
+ if isinstance(obj, dict):
4854
+ keys = list(obj.keys())
4855
+ if isinstance(idx, int) and 0 <= idx < len(keys):
4856
+ key = keys[idx]
4857
+ if var_count == 2:
4858
+ stack.append(key)
4859
+ stack.append(obj[key])
4860
+ else:
4861
+ stack.append(key)
4862
+ else:
4863
+ for _ in range(var_count):
4864
+ stack.append(None)
4865
+ elif isinstance(obj, ZMap):
4866
+ keys = list(obj.pairs.keys())
4867
+ if isinstance(idx, int) and 0 <= idx < len(keys):
4868
+ key = keys[idx]
4869
+ if var_count == 2:
4870
+ stack.append(key)
4871
+ stack.append(obj.pairs[key])
4872
+ else:
4873
+ stack.append(key)
4874
+ else:
4875
+ for _ in range(var_count):
4876
+ stack.append(None)
4877
+ elif isinstance(obj, (list, ZList)):
4878
+ elems = obj.elements if isinstance(obj, ZList) else obj
4879
+ if isinstance(idx, int) and 0 <= idx < len(elems):
4880
+ if var_count == 2:
4881
+ stack.append(idx)
4882
+ stack.append(elems[idx])
4883
+ else:
4884
+ stack.append(elems[idx])
4885
+ else:
4886
+ for _ in range(var_count):
4887
+ stack.append(None)
4888
+ else:
4889
+ try:
4890
+ if var_count == 2:
4891
+ stack.append(idx)
4892
+ stack.append(obj[idx] if obj is not None else None)
4893
+ else:
4894
+ stack.append(obj[idx] if obj is not None else None)
4895
+ except Exception:
4896
+ for _ in range(var_count):
4897
+ stack.append(None)
4898
+ except Exception:
4899
+ for _ in range(var_count):
4900
+ stack.append(None)
4288
4901
 
4289
4902
  elif op_name == "CALL_NAME":
4290
4903
  name_idx, arg_count = operand
4291
4904
  func_name = const(name_idx)
4292
4905
  args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
4293
- fn = _resolve(func_name) or self.builtins.get(func_name)
4294
- if fn is None:
4295
- res = self._call_fallback_builtin(func_name, args)
4906
+ # ── fast-path for native builtins ──
4907
+ native_res = self._vm_native_call(func_name, args)
4908
+ if native_res is not _VM_NATIVE_MISS:
4909
+ stack.append(native_res)
4296
4910
  else:
4297
- res = await self._invoke_callable_or_funcdesc(fn, args)
4298
- stack.append(res)
4911
+ fn = _resolve(func_name) or self.builtins.get(func_name)
4912
+ if fn is None:
4913
+ res = self._call_fallback_builtin(func_name, args)
4914
+ else:
4915
+ res = await self._invoke_callable_or_funcdesc(fn, args)
4916
+ stack.append(res)
4299
4917
 
4300
4918
  elif op_name == "CALL_BUILTIN":
4301
4919
  name_idx, arg_count = operand if isinstance(operand, (list, tuple)) else (operand, 0)
@@ -4367,7 +4985,12 @@ class VM:
4367
4985
  b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
4368
4986
  if hasattr(a, 'value'): a = a.value
4369
4987
  if hasattr(b, 'value'): b = b.value
4370
- stack.append(a / b if b != 0 else 0)
4988
+ if b == 0:
4989
+ raise VMRuntimeError("Division by zero")
4990
+ if isinstance(a, int) and isinstance(b, int) and not isinstance(a, bool) and not isinstance(b, bool):
4991
+ stack.append(a // b if a % b == 0 else a / b)
4992
+ else:
4993
+ stack.append(a / b)
4371
4994
  elif op_name == "MOD":
4372
4995
  b = _unwrap(stack.pop() if stack else 1)
4373
4996
  a = _unwrap(stack.pop() if stack else 0)
@@ -4434,11 +5057,15 @@ class VM:
4434
5057
  stack.append(elements)
4435
5058
  elif op_name == "BUILD_MAP":
4436
5059
  count = operand if operand is not None else 0
4437
- result = {}
5060
+ pairs = []
4438
5061
  for _ in range(count):
4439
5062
  val = stack.pop() if stack else None
4440
5063
  key = stack.pop() if stack else None
4441
- result[key] = val
5064
+ pairs.append((key, val))
5065
+ pairs.reverse()
5066
+ result = {}
5067
+ for k, v in pairs:
5068
+ result[k] = v
4442
5069
  stack.append(result)
4443
5070
  elif op_name == "BUILD_SET":
4444
5071
  count = operand if operand is not None else 0
@@ -4452,6 +5079,16 @@ class VM:
4452
5079
  stack.append(obj.get(idx))
4453
5080
  elif isinstance(obj, ZMap):
4454
5081
  stack.append(obj.get(idx))
5082
+ elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
5083
+ idx_str = idx.value if hasattr(idx, 'value') else str(idx) if idx is not None else None
5084
+ val = obj.get(idx_str) if idx_str else None
5085
+ stack.append(self._unwrap_after_builtin(val) if val is not None else None)
5086
+ elif isinstance(obj, dict) and isinstance(idx, int) and not isinstance(idx, bool):
5087
+ keys = list(obj.keys())
5088
+ if 0 <= idx < len(keys):
5089
+ stack.append(obj[keys[idx]])
5090
+ else:
5091
+ stack.append(None)
4455
5092
  elif isinstance(obj, ZString):
4456
5093
  stack.append(obj[idx])
4457
5094
  else:
@@ -4459,6 +5096,59 @@ class VM:
4459
5096
  stack.append(val)
4460
5097
  except (IndexError, KeyError, TypeError):
4461
5098
  stack.append(None)
5099
+ elif op_name == "FOR_ITER":
5100
+ var_count = operand if operand else 1
5101
+ idx = stack.pop() if stack else 0
5102
+ obj = stack.pop() if stack else None
5103
+ try:
5104
+ if isinstance(obj, dict):
5105
+ keys = list(obj.keys())
5106
+ if isinstance(idx, int) and 0 <= idx < len(keys):
5107
+ key = keys[idx]
5108
+ if var_count == 2:
5109
+ stack.append(key)
5110
+ stack.append(obj[key])
5111
+ else:
5112
+ stack.append(key)
5113
+ else:
5114
+ for _ in range(var_count):
5115
+ stack.append(None)
5116
+ elif isinstance(obj, ZMap):
5117
+ keys = list(obj.pairs.keys())
5118
+ if isinstance(idx, int) and 0 <= idx < len(keys):
5119
+ key = keys[idx]
5120
+ if var_count == 2:
5121
+ stack.append(key)
5122
+ stack.append(obj.pairs[key])
5123
+ else:
5124
+ stack.append(key)
5125
+ else:
5126
+ for _ in range(var_count):
5127
+ stack.append(None)
5128
+ elif isinstance(obj, (list, ZList)):
5129
+ elems = obj.elements if isinstance(obj, ZList) else obj
5130
+ if isinstance(idx, int) and 0 <= idx < len(elems):
5131
+ if var_count == 2:
5132
+ stack.append(idx)
5133
+ stack.append(elems[idx])
5134
+ else:
5135
+ stack.append(elems[idx])
5136
+ else:
5137
+ for _ in range(var_count):
5138
+ stack.append(None)
5139
+ else:
5140
+ try:
5141
+ if var_count == 2:
5142
+ stack.append(idx)
5143
+ stack.append(obj[idx] if obj is not None else None)
5144
+ else:
5145
+ stack.append(obj[idx] if obj is not None else None)
5146
+ except Exception:
5147
+ for _ in range(var_count):
5148
+ stack.append(None)
5149
+ except Exception:
5150
+ for _ in range(var_count):
5151
+ stack.append(None)
4462
5152
  elif op_name == "SLICE":
4463
5153
  end = _unwrap(stack.pop() if stack else None)
4464
5154
  start = _unwrap(stack.pop() if stack else None)
@@ -4637,8 +5327,8 @@ class VM:
4637
5327
  if callable(export_fn):
4638
5328
  try:
4639
5329
  export_fn(name, value)
4640
- except Exception:
4641
- pass
5330
+ except Exception as _e:
5331
+ _vm_warn("EXPORT", f"export_fn('{name}') call failed (async path)", _e)
4642
5332
  else:
4643
5333
  self.env[name] = value
4644
5334
  self._bump_env_version(name, value)
@@ -4655,8 +5345,8 @@ class VM:
4655
5345
  env_dir = self.env.get("__DIR__", None)
4656
5346
  if env_dir:
4657
5347
  sandbox = _os_w.path.realpath(str(env_dir))
4658
- except Exception:
4659
- pass
5348
+ except Exception as _e:
5349
+ _vm_warn("SANDBOX", "Failed to resolve sandbox __DIR__ for WRITE", _e)
4660
5350
 
4661
5351
  path_str = str(path)
4662
5352
  if "\x00" in path_str:
@@ -4848,7 +5538,7 @@ class VM:
4848
5538
  ip = handler
4849
5539
  else:
4850
5540
  msg = exc.value if hasattr(exc, "value") else exc
4851
- raise ZEvaluationError(str(msg))
5541
+ raise VMRuntimeError(str(msg))
4852
5542
 
4853
5543
  elif op_name == "TX_COMMIT":
4854
5544
  if self.env.get("_in_transaction", False):
@@ -4909,7 +5599,7 @@ class VM:
4909
5599
  if self.env.get("_in_transaction", False):
4910
5600
  self.env["_blockchain_state"] = dict(self.env.get("_tx_snapshot", {}))
4911
5601
  self.env["_in_transaction"] = False
4912
- raise ZEvaluationError(
5602
+ raise VMRuntimeError(
4913
5603
  f"Out of gas: required {amount}, remaining {current}")
4914
5604
  self.env["_gas_remaining"] = new_gas
4915
5605
 
@@ -4930,7 +5620,7 @@ class VM:
4930
5620
  tx_stack = self.env.get("_tx_stack", [])
4931
5621
  if tx_stack: tx_stack.pop()
4932
5622
  self.env["_in_transaction"] = bool(tx_stack)
4933
- raise ZEvaluationError(f"Requirement failed: {message}")
5623
+ raise VMRuntimeError(f"Requirement failed: {message}")
4934
5624
 
4935
5625
  elif op_name == "DEFINE_CONTRACT":
4936
5626
  contract_obj = self._build_smart_contract(
@@ -4941,19 +5631,11 @@ class VM:
4941
5631
  stack.append(contract_obj)
4942
5632
 
4943
5633
  elif op_name == "DEFINE_ENTITY":
4944
- member_count = operand
4945
- members = {}
4946
- for _ in range(member_count):
4947
- key_obj = stack.pop() if stack else None
4948
- val_obj = stack.pop() if stack else None
4949
- key_str = key_obj.value if hasattr(key_obj, 'value') else str(key_obj)
4950
- members[key_str] = val_obj
4951
-
4952
- name_obj = stack.pop() if stack else None
4953
- # Create Entity (using Map for now, can be specialized Entity class later)
4954
- members['_type'] = 'entity'
4955
- members['_name'] = name_obj.value if hasattr(name_obj, 'value') else str(name_obj)
4956
- stack.append(ZMap(members))
5634
+ entity_obj = self._build_entity_definition(
5635
+ operand, stack, lambda: stack.pop() if stack else None,
5636
+ lambda idx: consts[idx] if isinstance(idx, int) and 0 <= idx < len(consts) else idx,
5637
+ )
5638
+ stack.append(entity_obj)
4957
5639
 
4958
5640
  elif op_name == "DEFINE_CAPABILITY":
4959
5641
  name = stack.pop() if stack else None
@@ -5034,13 +5716,13 @@ class VM:
5034
5716
  if owner is not None:
5035
5717
  owner_val = owner.value if hasattr(owner, 'value') else str(owner)
5036
5718
  if caller and caller != owner_val:
5037
- raise ZEvaluationError(
5719
+ raise VMRuntimeError(
5038
5720
  f"Access denied: '{r_key}' restricted to owner only"
5039
5721
  )
5040
5722
  elif isinstance(restriction_val, (list, tuple)):
5041
5723
  allowed = [a.value if hasattr(a, 'value') else str(a) for a in restriction_val]
5042
5724
  if caller and caller not in allowed:
5043
- raise ZEvaluationError(
5725
+ raise VMRuntimeError(
5044
5726
  f"Access denied: '{r_key}' restricted to allowed addresses"
5045
5727
  )
5046
5728
 
@@ -5064,6 +5746,13 @@ class VM:
5064
5746
  elapsed = time.perf_counter() - instr_start_time
5065
5747
  self.profiler.measure_instruction(current_ip, elapsed)
5066
5748
  except Exception as e:
5749
+ # Route Python exceptions through the try_stack (Zexus try/catch)
5750
+ if try_stack:
5751
+ handler_ip = try_stack.pop()
5752
+ exc_msg = e.args[0] if e.args else str(e)
5753
+ stack.append(str(exc_msg))
5754
+ ip = handler_ip
5755
+ continue
5067
5756
  if self.env.get("_continue_on_error", False):
5068
5757
  # Error Recovery Mode
5069
5758
  if debug: print(f"[VM ERROR RECOVERY] {e}")
@@ -5082,9 +5771,9 @@ class VM:
5082
5771
  if tx_stack:
5083
5772
  tx_stack.pop()
5084
5773
  self.env["_in_transaction"] = bool(tx_stack)
5085
- except Exception:
5086
- # Best-effort revert; never swallow the original exception.
5087
- pass
5774
+ except Exception as _e:
5775
+ # Best-effort revert; log but never swallow the original exception.
5776
+ _vm_warn("TX", "Transaction revert cleanup failed (best-effort)", _e)
5088
5777
  raise
5089
5778
 
5090
5779
  if profile_ops and opcode_counts is not None:
@@ -5099,10 +5788,15 @@ class VM:
5099
5788
  # ==================== Helpers ====================
5100
5789
 
5101
5790
  async def _invoke_callable_or_funcdesc(self, fn, args, is_constant=False):
5791
+ # 0. Entity construction
5792
+ from ..object import EntityDefinition as _EntityDef
5793
+ if isinstance(fn, _EntityDef):
5794
+ return self._construct_entity(fn, args)
5795
+
5102
5796
  # 1. Function Descriptor (VM Bytecode Closure)
5103
5797
  if isinstance(fn, dict) and "bytecode" in fn:
5104
5798
  func_bc = fn["bytecode"]
5105
- params = fn.get("params", [])
5799
+ params = fn.get("params") or fn.get("parameters") or []
5106
5800
  is_async = fn.get("is_async", False)
5107
5801
  # Use captured parent_vm (closure), fallback to self
5108
5802
  parent_env = fn.get("parent_vm", self)
@@ -5241,6 +5935,76 @@ class VM:
5241
5935
  print(f"[VM] fallback builtin '{name}' failed: {exc}")
5242
5936
  return ZEvaluationError(f"Builtin '{name}' failed: {exc}")
5243
5937
 
5938
+ def _vm_native_call(self, name: str, args: list):
5939
+ """Fast-path native handling of common builtins that operate on VM-native
5940
+ types (plain Python list/dict/str/int) WITHOUT wrapping to ZList/ZMap.
5941
+
5942
+ Returns _VM_NATIVE_MISS if no native handler exists, letting the caller
5943
+ fall through to the evaluator builtin path.
5944
+ """
5945
+ if name == "push":
5946
+ # push(list, item) → return new list with item appended (non-mutating)
5947
+ if len(args) == 2:
5948
+ target = args[0]
5949
+ item = args[1]
5950
+ if isinstance(target, list):
5951
+ return target + [item]
5952
+ if isinstance(target, ZList):
5953
+ return ZList(target.elements + [item])
5954
+ return _VM_NATIVE_MISS
5955
+ if name == "append":
5956
+ # append(list, item) → mutate list in-place, return list
5957
+ if len(args) == 2:
5958
+ target = args[0]
5959
+ item = args[1]
5960
+ if isinstance(target, list):
5961
+ target.append(item)
5962
+ return target
5963
+ if isinstance(target, ZList):
5964
+ target.elements.append(item)
5965
+ return target
5966
+ return _VM_NATIVE_MISS
5967
+ if name == "length" or name == "len":
5968
+ if len(args) == 1:
5969
+ obj = args[0]
5970
+ if isinstance(obj, list):
5971
+ return len(obj)
5972
+ if isinstance(obj, ZList):
5973
+ return len(obj.elements)
5974
+ if isinstance(obj, (str, ZString)):
5975
+ val = obj.value if isinstance(obj, ZString) else obj
5976
+ return len(val)
5977
+ if isinstance(obj, (dict, ZMap)):
5978
+ if isinstance(obj, ZMap):
5979
+ return len(obj.pairs)
5980
+ return len(obj)
5981
+ if hasattr(obj, '__len__'):
5982
+ return len(obj)
5983
+ return 0
5984
+ return _VM_NATIVE_MISS
5985
+ if name == "str" or name == "string":
5986
+ if len(args) == 1:
5987
+ return self._format_print_value(args[0])
5988
+ return _VM_NATIVE_MISS
5989
+ if name == "range":
5990
+ # range(n) → [0..n-1] or range(start, end) → [start..end-1]
5991
+ if len(args) == 1:
5992
+ n = args[0]
5993
+ if isinstance(n, ZInteger): n = n.value
5994
+ if hasattr(n, 'value'): n = n.value
5995
+ if isinstance(n, (int, float)):
5996
+ return list(range(int(n)))
5997
+ elif len(args) == 2:
5998
+ a, b = args
5999
+ if isinstance(a, ZInteger): a = a.value
6000
+ if isinstance(b, ZInteger): b = b.value
6001
+ if hasattr(a, 'value'): a = a.value
6002
+ if hasattr(b, 'value'): b = b.value
6003
+ if isinstance(a, (int, float)) and isinstance(b, (int, float)):
6004
+ return list(range(int(a), int(b)))
6005
+ return _VM_NATIVE_MISS
6006
+ return _VM_NATIVE_MISS
6007
+
5244
6008
  def profile_execution(self, bytecode, iterations: int = 1000) -> Dict[str, Any]:
5245
6009
  """Profile execution performance across available modes"""
5246
6010
  import timeit
@@ -5464,6 +6228,15 @@ def create_vm(mode: str = "auto", use_jit: bool = True, **kwargs) -> VM:
5464
6228
  return VM(mode=VMMode(mode.lower()), use_jit=use_jit, **kwargs)
5465
6229
 
5466
6230
 
6231
+ def _vm_div(a, b):
6232
+ """Division with proper integer handling and zero-division errors."""
6233
+ if b == 0:
6234
+ raise VMRuntimeError("Division by zero")
6235
+ if isinstance(a, int) and not isinstance(a, bool) and isinstance(b, int) and not isinstance(b, bool):
6236
+ return a // b if a % b == 0 else a / b
6237
+ return a / b
6238
+
6239
+
5467
6240
  def _safe_pow(a, b, *, max_exp: int = 10000, max_bits: int = 8192):
5468
6241
  """Bound exponentiation to avoid exponent DoS.
5469
6242