zexus 1.6.8 → 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 +12 -5
- 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/capability_system.py +184 -9
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +383 -34
- 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/bytecode.py +124 -7
- package/src/zexus/compiler/compat_runtime.py +6 -2
- package/src/zexus/compiler/lexer.py +16 -5
- package/src/zexus/compiler/parser.py +108 -7
- package/src/zexus/compiler/semantic.py +18 -19
- package/src/zexus/compiler/zexus_ast.py +26 -1
- 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 +112 -9
- 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 +457 -37
- package/src/zexus/evaluator/core.py +644 -50
- package/src/zexus/evaluator/expressions.py +358 -62
- package/src/zexus/evaluator/functions.py +458 -20
- package/src/zexus/evaluator/resource_limiter.py +4 -4
- package/src/zexus/evaluator/statements.py +774 -122
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/evaluator_original.py +1 -1
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -458
- 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 +239 -9
- package/src/zexus/module_manager.py +129 -1
- package/src/zexus/object.py +76 -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 +1349 -408
- package/src/zexus/parser/strategy_context.py +755 -58
- package/src/zexus/parser/strategy_structural.py +121 -21
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +61 -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/backend.py +261 -0
- package/src/zexus/renderer/canvas.py +78 -0
- package/src/zexus/renderer/color_system.py +201 -0
- package/src/zexus/renderer/graphics.py +31 -0
- package/src/zexus/renderer/layout.py +222 -0
- package/src/zexus/renderer/main_renderer.py +66 -0
- package/src/zexus/renderer/painter.py +30 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__init__.py +10 -2
- 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/runtime/load_manager.py +368 -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 +80 -6
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +59 -11
- 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 +561 -17
- package/src/zexus/vm/compiler.py +818 -51
- 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 +364 -20
- 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 +140 -45
- 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 +3581 -531
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +137 -11
- package/src/zexus/zexus_token.py +16 -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 +16 -6
- package/src/zexus.egg-info/SOURCES.txt +129 -17
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zexus VM — Binary Bytecode Format (Phase 1)
|
|
3
|
+
|
|
4
|
+
Compact binary representation of Zexus bytecode that can be deserialized
|
|
5
|
+
by both Python and Rust without GIL overhead.
|
|
6
|
+
|
|
7
|
+
File Format (.zxc)
|
|
8
|
+
==================
|
|
9
|
+
|
|
10
|
+
Header (16 bytes):
|
|
11
|
+
magic: 4 bytes b"ZXC\\x00"
|
|
12
|
+
version: 2 bytes uint16 LE (currently 1)
|
|
13
|
+
flags: 2 bytes uint16 LE (reserved)
|
|
14
|
+
n_consts: 4 bytes uint32 LE (number of constants)
|
|
15
|
+
n_instrs: 4 bytes uint32 LE (number of instructions)
|
|
16
|
+
|
|
17
|
+
Constants Section:
|
|
18
|
+
For each constant:
|
|
19
|
+
tag: 1 byte (ConstTag enum)
|
|
20
|
+
data: variable (depends on tag)
|
|
21
|
+
|
|
22
|
+
ConstTag values:
|
|
23
|
+
0x00 NULL — no data
|
|
24
|
+
0x01 BOOL — 1 byte (0=false, 1=true)
|
|
25
|
+
0x02 INT32 — 4 bytes int32 LE
|
|
26
|
+
0x03 INT64 — 8 bytes int64 LE
|
|
27
|
+
0x04 FLOAT64 — 8 bytes float64 LE (IEEE 754)
|
|
28
|
+
0x05 STRING — 4 bytes uint32 LE (length) + UTF-8 bytes
|
|
29
|
+
0x06 FUNC_DESC — 4 bytes uint32 LE (length) + UTF-8 bytes (JSON-encoded)
|
|
30
|
+
0x07 LIST — 4 bytes uint32 LE (count) + count × constant entries (recursive)
|
|
31
|
+
0x08 MAP — 4 bytes uint32 LE (count) + count × (key_const, val_const) entries
|
|
32
|
+
0xFF OPAQUE — 4 bytes uint32 LE (length) + raw bytes (not Rust-readable)
|
|
33
|
+
|
|
34
|
+
Instructions Section:
|
|
35
|
+
For each instruction (variable-width):
|
|
36
|
+
opcode: 2 bytes uint16 LE (Opcode IntEnum value)
|
|
37
|
+
op_type: 1 byte (OperandType enum)
|
|
38
|
+
operand: variable (depends on op_type)
|
|
39
|
+
|
|
40
|
+
OperandType values:
|
|
41
|
+
0x00 NONE — no operand data
|
|
42
|
+
0x01 U32 — 4 bytes uint32 LE (constant index, jump target, etc.)
|
|
43
|
+
0x02 I64 — 8 bytes int64 LE (immediate integer)
|
|
44
|
+
0x03 PAIR_U32 — 8 bytes (2 × uint32 LE) — e.g. (name_idx, arg_count)
|
|
45
|
+
0x04 TRIPLE_U32 — 12 bytes (3 × uint32 LE) — e.g. register ops (r1, r2, r3)
|
|
46
|
+
|
|
47
|
+
Footer (optional, for integrity):
|
|
48
|
+
checksum: 4 bytes CRC32 of everything before the checksum
|
|
49
|
+
|
|
50
|
+
Usage
|
|
51
|
+
-----
|
|
52
|
+
::
|
|
53
|
+
|
|
54
|
+
from zexus.vm.binary_bytecode import serialize, deserialize
|
|
55
|
+
|
|
56
|
+
# Convert Bytecode → bytes
|
|
57
|
+
data = serialize(bytecode_obj)
|
|
58
|
+
|
|
59
|
+
# Convert bytes → Bytecode
|
|
60
|
+
bytecode_obj = deserialize(data)
|
|
61
|
+
|
|
62
|
+
# File I/O
|
|
63
|
+
save_zxc("contract.zxc", bytecode_obj)
|
|
64
|
+
bytecode_obj = load_zxc("contract.zxc")
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
from __future__ import annotations
|
|
68
|
+
|
|
69
|
+
import struct
|
|
70
|
+
import json
|
|
71
|
+
import zlib
|
|
72
|
+
from enum import IntEnum
|
|
73
|
+
from typing import Any, List, Tuple, Optional, Dict
|
|
74
|
+
|
|
75
|
+
from .bytecode import Bytecode, Opcode
|
|
76
|
+
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
# Constants
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
ZXC_MAGIC = b"ZXC\x00"
|
|
82
|
+
ZXC_VERSION = 1
|
|
83
|
+
ZXC_HEADER_SIZE = 16 # magic(4) + version(2) + flags(2) + n_consts(4) + n_instrs(4)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ConstTag(IntEnum):
|
|
87
|
+
"""Type tags for the constant pool."""
|
|
88
|
+
NULL = 0x00
|
|
89
|
+
BOOL = 0x01
|
|
90
|
+
INT32 = 0x02
|
|
91
|
+
INT64 = 0x03
|
|
92
|
+
FLOAT64 = 0x04
|
|
93
|
+
STRING = 0x05
|
|
94
|
+
FUNC_DESC = 0x06
|
|
95
|
+
LIST = 0x07
|
|
96
|
+
MAP = 0x08
|
|
97
|
+
OPAQUE = 0xFF
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class OperandType(IntEnum):
|
|
101
|
+
"""Operand encoding types for instructions."""
|
|
102
|
+
NONE = 0x00
|
|
103
|
+
U32 = 0x01
|
|
104
|
+
I64 = 0x02
|
|
105
|
+
PAIR_U32 = 0x03
|
|
106
|
+
TRIPLE_U32 = 0x04
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
# Serialization (Python Bytecode → Binary)
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
class _Writer:
|
|
114
|
+
"""Buffered binary writer."""
|
|
115
|
+
__slots__ = ("_parts",)
|
|
116
|
+
|
|
117
|
+
def __init__(self):
|
|
118
|
+
self._parts: List[bytes] = []
|
|
119
|
+
|
|
120
|
+
def write(self, data: bytes):
|
|
121
|
+
self._parts.append(data)
|
|
122
|
+
|
|
123
|
+
def u8(self, val: int):
|
|
124
|
+
self._parts.append(struct.pack("<B", val & 0xFF))
|
|
125
|
+
|
|
126
|
+
def u16(self, val: int):
|
|
127
|
+
self._parts.append(struct.pack("<H", val & 0xFFFF))
|
|
128
|
+
|
|
129
|
+
def u32(self, val: int):
|
|
130
|
+
self._parts.append(struct.pack("<I", val & 0xFFFFFFFF))
|
|
131
|
+
|
|
132
|
+
def i32(self, val: int):
|
|
133
|
+
self._parts.append(struct.pack("<i", val))
|
|
134
|
+
|
|
135
|
+
def i64(self, val: int):
|
|
136
|
+
self._parts.append(struct.pack("<q", val))
|
|
137
|
+
|
|
138
|
+
def f64(self, val: float):
|
|
139
|
+
self._parts.append(struct.pack("<d", val))
|
|
140
|
+
|
|
141
|
+
def string(self, s: str):
|
|
142
|
+
encoded = s.encode("utf-8")
|
|
143
|
+
self.u32(len(encoded))
|
|
144
|
+
self._parts.append(encoded)
|
|
145
|
+
|
|
146
|
+
def raw_bytes(self, data: bytes):
|
|
147
|
+
self.u32(len(data))
|
|
148
|
+
self._parts.append(data)
|
|
149
|
+
|
|
150
|
+
def getvalue(self) -> bytes:
|
|
151
|
+
return b"".join(self._parts)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _Reader:
|
|
155
|
+
"""Binary reader with position tracking."""
|
|
156
|
+
__slots__ = ("_data", "_pos")
|
|
157
|
+
|
|
158
|
+
def __init__(self, data: bytes):
|
|
159
|
+
self._data = data
|
|
160
|
+
self._pos = 0
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def remaining(self) -> int:
|
|
164
|
+
return len(self._data) - self._pos
|
|
165
|
+
|
|
166
|
+
def read(self, n: int) -> bytes:
|
|
167
|
+
end = self._pos + n
|
|
168
|
+
if end > len(self._data):
|
|
169
|
+
raise ValueError(f"Unexpected end of data at offset {self._pos}, need {n} bytes")
|
|
170
|
+
chunk = self._data[self._pos:end]
|
|
171
|
+
self._pos = end
|
|
172
|
+
return chunk
|
|
173
|
+
|
|
174
|
+
def u8(self) -> int:
|
|
175
|
+
return struct.unpack("<B", self.read(1))[0]
|
|
176
|
+
|
|
177
|
+
def u16(self) -> int:
|
|
178
|
+
return struct.unpack("<H", self.read(2))[0]
|
|
179
|
+
|
|
180
|
+
def u32(self) -> int:
|
|
181
|
+
return struct.unpack("<I", self.read(4))[0]
|
|
182
|
+
|
|
183
|
+
def i32(self) -> int:
|
|
184
|
+
return struct.unpack("<i", self.read(4))[0]
|
|
185
|
+
|
|
186
|
+
def i64(self) -> int:
|
|
187
|
+
return struct.unpack("<q", self.read(8))[0]
|
|
188
|
+
|
|
189
|
+
def f64(self) -> float:
|
|
190
|
+
return struct.unpack("<d", self.read(8))[0]
|
|
191
|
+
|
|
192
|
+
def string(self) -> str:
|
|
193
|
+
length = self.u32()
|
|
194
|
+
return self.read(length).decode("utf-8")
|
|
195
|
+
|
|
196
|
+
def raw_bytes(self) -> bytes:
|
|
197
|
+
length = self.u32()
|
|
198
|
+
return self.read(length)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _serialize_constant(w: _Writer, value: Any):
|
|
202
|
+
"""Serialize a single constant to the writer."""
|
|
203
|
+
if value is None:
|
|
204
|
+
w.u8(ConstTag.NULL)
|
|
205
|
+
elif isinstance(value, bool):
|
|
206
|
+
# Must check bool before int (bool is a subclass of int)
|
|
207
|
+
w.u8(ConstTag.BOOL)
|
|
208
|
+
w.u8(1 if value else 0)
|
|
209
|
+
elif isinstance(value, int):
|
|
210
|
+
if -(2**31) <= value < 2**31:
|
|
211
|
+
w.u8(ConstTag.INT32)
|
|
212
|
+
w.i32(value)
|
|
213
|
+
else:
|
|
214
|
+
w.u8(ConstTag.INT64)
|
|
215
|
+
w.i64(value)
|
|
216
|
+
elif isinstance(value, float):
|
|
217
|
+
w.u8(ConstTag.FLOAT64)
|
|
218
|
+
w.f64(value)
|
|
219
|
+
elif isinstance(value, str):
|
|
220
|
+
w.u8(ConstTag.STRING)
|
|
221
|
+
w.string(value)
|
|
222
|
+
elif isinstance(value, (list, tuple)):
|
|
223
|
+
# Check if it's a simple list of serializable values
|
|
224
|
+
w.u8(ConstTag.LIST)
|
|
225
|
+
w.u32(len(value))
|
|
226
|
+
for item in value:
|
|
227
|
+
_serialize_constant(w, item)
|
|
228
|
+
elif isinstance(value, dict):
|
|
229
|
+
# Serialize as JSON string for portability
|
|
230
|
+
try:
|
|
231
|
+
json_str = json.dumps(value, default=str)
|
|
232
|
+
w.u8(ConstTag.FUNC_DESC)
|
|
233
|
+
w.string(json_str)
|
|
234
|
+
except (TypeError, ValueError):
|
|
235
|
+
# Fall back to opaque
|
|
236
|
+
import pickle
|
|
237
|
+
data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
|
|
238
|
+
w.u8(ConstTag.OPAQUE)
|
|
239
|
+
w.raw_bytes(data)
|
|
240
|
+
else:
|
|
241
|
+
# Callable, AST nodes, or other Python objects → opaque
|
|
242
|
+
import pickle
|
|
243
|
+
try:
|
|
244
|
+
data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
|
|
245
|
+
w.u8(ConstTag.OPAQUE)
|
|
246
|
+
w.raw_bytes(data)
|
|
247
|
+
except (pickle.PicklingError, TypeError, AttributeError):
|
|
248
|
+
# Truly unserializable → store repr as string
|
|
249
|
+
w.u8(ConstTag.STRING)
|
|
250
|
+
w.string(repr(value))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _serialize_instruction(w: _Writer, opcode_val: int, operand: Any):
|
|
254
|
+
"""Serialize a single instruction to the writer."""
|
|
255
|
+
w.u16(opcode_val)
|
|
256
|
+
|
|
257
|
+
if operand is None:
|
|
258
|
+
w.u8(OperandType.NONE)
|
|
259
|
+
elif isinstance(operand, int):
|
|
260
|
+
if operand >= 0 and operand < 2**32:
|
|
261
|
+
w.u8(OperandType.U32)
|
|
262
|
+
w.u32(operand)
|
|
263
|
+
else:
|
|
264
|
+
w.u8(OperandType.I64)
|
|
265
|
+
w.i64(operand)
|
|
266
|
+
elif isinstance(operand, tuple):
|
|
267
|
+
if len(operand) == 2:
|
|
268
|
+
w.u8(OperandType.PAIR_U32)
|
|
269
|
+
w.u32(int(operand[0]) & 0xFFFFFFFF)
|
|
270
|
+
w.u32(int(operand[1]) & 0xFFFFFFFF)
|
|
271
|
+
elif len(operand) == 3:
|
|
272
|
+
w.u8(OperandType.TRIPLE_U32)
|
|
273
|
+
w.u32(int(operand[0]) & 0xFFFFFFFF)
|
|
274
|
+
w.u32(int(operand[1]) & 0xFFFFFFFF)
|
|
275
|
+
w.u32(int(operand[2]) & 0xFFFFFFFF)
|
|
276
|
+
else:
|
|
277
|
+
# Fallback — encode as I64 of first element
|
|
278
|
+
w.u8(OperandType.I64)
|
|
279
|
+
w.i64(int(operand[0]) if operand else 0)
|
|
280
|
+
elif isinstance(operand, str):
|
|
281
|
+
# String operands (label names, etc.) → treat as U32 after storing in constants
|
|
282
|
+
# This shouldn't normally happen after compilation, but handle gracefully
|
|
283
|
+
w.u8(OperandType.I64)
|
|
284
|
+
w.i64(hash(operand) & 0x7FFFFFFFFFFFFFFF)
|
|
285
|
+
else:
|
|
286
|
+
# Numeric-like
|
|
287
|
+
w.u8(OperandType.U32)
|
|
288
|
+
w.u32(int(operand) & 0xFFFFFFFF)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _resolve_opcode(op: Any) -> int:
|
|
292
|
+
"""Resolve an opcode to its integer value."""
|
|
293
|
+
if isinstance(op, int):
|
|
294
|
+
return op
|
|
295
|
+
if isinstance(op, Opcode):
|
|
296
|
+
return int(op)
|
|
297
|
+
if isinstance(op, str):
|
|
298
|
+
try:
|
|
299
|
+
return int(Opcode[op])
|
|
300
|
+
except KeyError:
|
|
301
|
+
# Unknown opcode → NOP
|
|
302
|
+
return int(Opcode.NOP)
|
|
303
|
+
return int(Opcode.NOP)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def serialize(bytecode: Bytecode, *, include_checksum: bool = True) -> bytes:
|
|
307
|
+
"""Serialize a Bytecode object to compact binary format.
|
|
308
|
+
|
|
309
|
+
Parameters
|
|
310
|
+
----------
|
|
311
|
+
bytecode : Bytecode
|
|
312
|
+
The bytecode to serialize.
|
|
313
|
+
include_checksum : bool
|
|
314
|
+
If True, append a CRC32 checksum.
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
bytes
|
|
319
|
+
The binary-encoded bytecode.
|
|
320
|
+
"""
|
|
321
|
+
w = _Writer()
|
|
322
|
+
|
|
323
|
+
consts = bytecode.constants or []
|
|
324
|
+
instrs = bytecode.instructions or []
|
|
325
|
+
|
|
326
|
+
# Header
|
|
327
|
+
w.write(ZXC_MAGIC)
|
|
328
|
+
w.u16(ZXC_VERSION)
|
|
329
|
+
w.u16(0) # flags (reserved)
|
|
330
|
+
w.u32(len(consts))
|
|
331
|
+
w.u32(len(instrs))
|
|
332
|
+
|
|
333
|
+
# Constants
|
|
334
|
+
for c in consts:
|
|
335
|
+
_serialize_constant(w, c)
|
|
336
|
+
|
|
337
|
+
# Instructions
|
|
338
|
+
for instr in instrs:
|
|
339
|
+
if instr is None:
|
|
340
|
+
continue
|
|
341
|
+
if isinstance(instr, tuple) and len(instr) >= 2:
|
|
342
|
+
op = instr[0]
|
|
343
|
+
operand = instr[1]
|
|
344
|
+
else:
|
|
345
|
+
op = instr
|
|
346
|
+
operand = None
|
|
347
|
+
opcode_val = _resolve_opcode(op)
|
|
348
|
+
_serialize_instruction(w, opcode_val, operand)
|
|
349
|
+
|
|
350
|
+
body = w.getvalue()
|
|
351
|
+
|
|
352
|
+
if include_checksum:
|
|
353
|
+
crc = zlib.crc32(body) & 0xFFFFFFFF
|
|
354
|
+
body += struct.pack("<I", crc)
|
|
355
|
+
|
|
356
|
+
return body
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
# Deserialization (Binary → Python Bytecode)
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
def _deserialize_constant(r: _Reader) -> Any:
|
|
364
|
+
"""Deserialize a single constant from the reader."""
|
|
365
|
+
tag = r.u8()
|
|
366
|
+
|
|
367
|
+
if tag == ConstTag.NULL:
|
|
368
|
+
return None
|
|
369
|
+
elif tag == ConstTag.BOOL:
|
|
370
|
+
return bool(r.u8())
|
|
371
|
+
elif tag == ConstTag.INT32:
|
|
372
|
+
return r.i32()
|
|
373
|
+
elif tag == ConstTag.INT64:
|
|
374
|
+
return r.i64()
|
|
375
|
+
elif tag == ConstTag.FLOAT64:
|
|
376
|
+
return r.f64()
|
|
377
|
+
elif tag == ConstTag.STRING:
|
|
378
|
+
return r.string()
|
|
379
|
+
elif tag == ConstTag.FUNC_DESC:
|
|
380
|
+
json_str = r.string()
|
|
381
|
+
return json.loads(json_str)
|
|
382
|
+
elif tag == ConstTag.LIST:
|
|
383
|
+
count = r.u32()
|
|
384
|
+
return [_deserialize_constant(r) for _ in range(count)]
|
|
385
|
+
elif tag == ConstTag.MAP:
|
|
386
|
+
count = r.u32()
|
|
387
|
+
result = {}
|
|
388
|
+
for _ in range(count):
|
|
389
|
+
key = _deserialize_constant(r)
|
|
390
|
+
val = _deserialize_constant(r)
|
|
391
|
+
result[key] = val
|
|
392
|
+
return result
|
|
393
|
+
elif tag == ConstTag.OPAQUE:
|
|
394
|
+
import pickle
|
|
395
|
+
data = r.raw_bytes()
|
|
396
|
+
return pickle.loads(data)
|
|
397
|
+
else:
|
|
398
|
+
raise ValueError(f"Unknown constant tag: 0x{tag:02x}")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _deserialize_instruction(r: _Reader) -> Tuple[Any, Any]:
|
|
402
|
+
"""Deserialize a single instruction from the reader."""
|
|
403
|
+
opcode_val = r.u16()
|
|
404
|
+
op_type = r.u8()
|
|
405
|
+
|
|
406
|
+
# Resolve opcode to Opcode enum
|
|
407
|
+
try:
|
|
408
|
+
opcode = Opcode(opcode_val)
|
|
409
|
+
except ValueError:
|
|
410
|
+
opcode = opcode_val # Unknown opcode — keep as int
|
|
411
|
+
|
|
412
|
+
if op_type == OperandType.NONE:
|
|
413
|
+
return (opcode, None)
|
|
414
|
+
elif op_type == OperandType.U32:
|
|
415
|
+
return (opcode, r.u32())
|
|
416
|
+
elif op_type == OperandType.I64:
|
|
417
|
+
return (opcode, r.i64())
|
|
418
|
+
elif op_type == OperandType.PAIR_U32:
|
|
419
|
+
a = r.u32()
|
|
420
|
+
b = r.u32()
|
|
421
|
+
return (opcode, (a, b))
|
|
422
|
+
elif op_type == OperandType.TRIPLE_U32:
|
|
423
|
+
a = r.u32()
|
|
424
|
+
b = r.u32()
|
|
425
|
+
c = r.u32()
|
|
426
|
+
return (opcode, (a, b, c))
|
|
427
|
+
else:
|
|
428
|
+
raise ValueError(f"Unknown operand type: 0x{op_type:02x}")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def deserialize(data: bytes, *, verify_checksum: bool = True) -> Bytecode:
|
|
432
|
+
"""Deserialize binary data into a Bytecode object.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
data : bytes
|
|
437
|
+
The binary-encoded bytecode.
|
|
438
|
+
verify_checksum : bool
|
|
439
|
+
If True, verify the trailing CRC32 checksum.
|
|
440
|
+
|
|
441
|
+
Returns
|
|
442
|
+
-------
|
|
443
|
+
Bytecode
|
|
444
|
+
The deserialized Bytecode object.
|
|
445
|
+
|
|
446
|
+
Raises
|
|
447
|
+
------
|
|
448
|
+
ValueError
|
|
449
|
+
If the data is malformed or the checksum doesn't match.
|
|
450
|
+
"""
|
|
451
|
+
if len(data) < ZXC_HEADER_SIZE:
|
|
452
|
+
raise ValueError(f"Data too short for header: {len(data)} bytes")
|
|
453
|
+
|
|
454
|
+
# Verify checksum first (last 4 bytes)
|
|
455
|
+
if verify_checksum and len(data) > ZXC_HEADER_SIZE + 4:
|
|
456
|
+
body = data[:-4]
|
|
457
|
+
stored_crc = struct.unpack("<I", data[-4:])[0]
|
|
458
|
+
computed_crc = zlib.crc32(body) & 0xFFFFFFFF
|
|
459
|
+
if stored_crc != computed_crc:
|
|
460
|
+
raise ValueError(
|
|
461
|
+
f"Checksum mismatch: stored=0x{stored_crc:08x}, "
|
|
462
|
+
f"computed=0x{computed_crc:08x}"
|
|
463
|
+
)
|
|
464
|
+
data = body # Strip checksum for reading
|
|
465
|
+
|
|
466
|
+
r = _Reader(data)
|
|
467
|
+
|
|
468
|
+
# Header
|
|
469
|
+
magic = r.read(4)
|
|
470
|
+
if magic != ZXC_MAGIC:
|
|
471
|
+
raise ValueError(f"Invalid magic: {magic!r}, expected {ZXC_MAGIC!r}")
|
|
472
|
+
|
|
473
|
+
version = r.u16()
|
|
474
|
+
if version > ZXC_VERSION:
|
|
475
|
+
raise ValueError(f"Unsupported version: {version} (max supported: {ZXC_VERSION})")
|
|
476
|
+
|
|
477
|
+
_flags = r.u16() # reserved
|
|
478
|
+
n_consts = r.u32()
|
|
479
|
+
n_instrs = r.u32()
|
|
480
|
+
|
|
481
|
+
# Constants
|
|
482
|
+
constants = [_deserialize_constant(r) for _ in range(n_consts)]
|
|
483
|
+
|
|
484
|
+
# Instructions
|
|
485
|
+
instructions = [_deserialize_instruction(r) for _ in range(n_instrs)]
|
|
486
|
+
|
|
487
|
+
# Build Bytecode object
|
|
488
|
+
bc = Bytecode(instructions=instructions, constants=constants)
|
|
489
|
+
bc.metadata["source_file"] = None
|
|
490
|
+
bc.metadata["version"] = f"binary-{version}"
|
|
491
|
+
bc.metadata["created_by"] = "binary_deserializer"
|
|
492
|
+
|
|
493
|
+
return bc
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# ---------------------------------------------------------------------------
|
|
497
|
+
# File I/O
|
|
498
|
+
# ---------------------------------------------------------------------------
|
|
499
|
+
|
|
500
|
+
def save_zxc(path: str, bytecode: Bytecode, *, include_checksum: bool = True):
|
|
501
|
+
"""Save a Bytecode object to a .zxc file.
|
|
502
|
+
|
|
503
|
+
Parameters
|
|
504
|
+
----------
|
|
505
|
+
path : str
|
|
506
|
+
Output file path (typically ending in `.zxc`).
|
|
507
|
+
bytecode : Bytecode
|
|
508
|
+
The bytecode to save.
|
|
509
|
+
include_checksum : bool
|
|
510
|
+
If True, append CRC32 checksum.
|
|
511
|
+
"""
|
|
512
|
+
data = serialize(bytecode, include_checksum=include_checksum)
|
|
513
|
+
with open(path, "wb") as f:
|
|
514
|
+
f.write(data)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def load_zxc(path: str, *, verify_checksum: bool = True) -> Bytecode:
|
|
518
|
+
"""Load a Bytecode object from a .zxc file.
|
|
519
|
+
|
|
520
|
+
Parameters
|
|
521
|
+
----------
|
|
522
|
+
path : str
|
|
523
|
+
Input file path.
|
|
524
|
+
verify_checksum : bool
|
|
525
|
+
If True, verify CRC32 checksum.
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
Bytecode
|
|
530
|
+
The deserialized Bytecode object.
|
|
531
|
+
"""
|
|
532
|
+
with open(path, "rb") as f:
|
|
533
|
+
data = f.read()
|
|
534
|
+
return deserialize(data, verify_checksum=verify_checksum)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
# ---------------------------------------------------------------------------
|
|
538
|
+
# Utility — size comparison
|
|
539
|
+
# ---------------------------------------------------------------------------
|
|
540
|
+
|
|
541
|
+
def compare_sizes(bytecode: Bytecode) -> Dict[str, Any]:
|
|
542
|
+
"""Compare binary size vs Python tuple representation size.
|
|
543
|
+
|
|
544
|
+
Returns a dict with size information for analysis.
|
|
545
|
+
"""
|
|
546
|
+
import sys
|
|
547
|
+
|
|
548
|
+
binary = serialize(bytecode)
|
|
549
|
+
binary_size = len(binary)
|
|
550
|
+
|
|
551
|
+
# Estimate Python tuple size
|
|
552
|
+
py_size = sys.getsizeof(bytecode.instructions)
|
|
553
|
+
for instr in bytecode.instructions:
|
|
554
|
+
py_size += sys.getsizeof(instr)
|
|
555
|
+
if isinstance(instr, tuple):
|
|
556
|
+
for item in instr:
|
|
557
|
+
py_size += sys.getsizeof(item)
|
|
558
|
+
py_size += sys.getsizeof(bytecode.constants)
|
|
559
|
+
for c in bytecode.constants:
|
|
560
|
+
py_size += sys.getsizeof(c)
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
"binary_bytes": binary_size,
|
|
564
|
+
"python_bytes": py_size,
|
|
565
|
+
"ratio": binary_size / py_size if py_size > 0 else 0.0,
|
|
566
|
+
"n_instructions": len(bytecode.instructions),
|
|
567
|
+
"n_constants": len(bytecode.constants),
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
# ---------------------------------------------------------------------------
|
|
572
|
+
# Multi-Bytecode Container (for file-level caching)
|
|
573
|
+
# ---------------------------------------------------------------------------
|
|
574
|
+
#
|
|
575
|
+
# Format: ZXCM magic (4B) + count (4B) + [length(4B) + zxc_data]*count
|
|
576
|
+
#
|
|
577
|
+
|
|
578
|
+
ZXCM_MAGIC = b"ZXCM"
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def serialize_multi(bytecodes: List[Bytecode], *, include_checksum: bool = True) -> bytes:
|
|
582
|
+
"""Serialize a list of Bytecode objects into a single binary blob.
|
|
583
|
+
|
|
584
|
+
Used for file-level caching where multiple Bytecodes represent
|
|
585
|
+
individual statements from a single source file.
|
|
586
|
+
"""
|
|
587
|
+
w = _Writer()
|
|
588
|
+
w.write(ZXCM_MAGIC)
|
|
589
|
+
w.u32(len(bytecodes))
|
|
590
|
+
for bc in bytecodes:
|
|
591
|
+
chunk = serialize(bc, include_checksum=include_checksum)
|
|
592
|
+
w.u32(len(chunk))
|
|
593
|
+
w.write(chunk)
|
|
594
|
+
return w.getvalue()
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def deserialize_multi(data: bytes, *, verify_checksum: bool = True) -> List[Bytecode]:
|
|
598
|
+
"""Deserialize a multi-bytecode container back to a list of Bytecodes."""
|
|
599
|
+
if len(data) < 8:
|
|
600
|
+
raise ValueError("Multi-bytecode data too short")
|
|
601
|
+
if data[:4] != ZXCM_MAGIC:
|
|
602
|
+
raise ValueError(f"Invalid multi-bytecode magic: {data[:4]!r}")
|
|
603
|
+
count = struct.unpack_from("<I", data, 4)[0]
|
|
604
|
+
offset = 8
|
|
605
|
+
result: List[Bytecode] = []
|
|
606
|
+
for _ in range(count):
|
|
607
|
+
if offset + 4 > len(data):
|
|
608
|
+
raise ValueError("Truncated multi-bytecode container")
|
|
609
|
+
chunk_len = struct.unpack_from("<I", data, offset)[0]
|
|
610
|
+
offset += 4
|
|
611
|
+
if offset + chunk_len > len(data):
|
|
612
|
+
raise ValueError("Truncated multi-bytecode chunk")
|
|
613
|
+
chunk = data[offset:offset + chunk_len]
|
|
614
|
+
result.append(deserialize(chunk, verify_checksum=verify_checksum))
|
|
615
|
+
offset += chunk_len
|
|
616
|
+
return result
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def save_zxcm(path: str, bytecodes: List[Bytecode]):
|
|
620
|
+
"""Save multiple Bytecodes to a .zxc multi-container file."""
|
|
621
|
+
data = serialize_multi(bytecodes)
|
|
622
|
+
with open(path, "wb") as f:
|
|
623
|
+
f.write(data)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def load_zxcm(path: str) -> List[Bytecode]:
|
|
627
|
+
"""Load multiple Bytecodes from a .zxc multi-container file."""
|
|
628
|
+
with open(path, "rb") as f:
|
|
629
|
+
data = f.read()
|
|
630
|
+
return deserialize_multi(data)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
# ---------------------------------------------------------------------------
|
|
634
|
+
# Co-located .zxc helper
|
|
635
|
+
# ---------------------------------------------------------------------------
|
|
636
|
+
|
|
637
|
+
import os
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def zxc_path_for(source_path: str) -> str:
|
|
641
|
+
"""Return the .zxc path co-located with a .zx source file.
|
|
642
|
+
|
|
643
|
+
``foo.zx`` → ``foo.zxc``
|
|
644
|
+
``bar.zexus`` → ``bar.zxc``
|
|
645
|
+
Other extensions just append ``.zxc``.
|
|
646
|
+
"""
|
|
647
|
+
root, ext = os.path.splitext(source_path)
|
|
648
|
+
return root + ".zxc"
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def is_zxc_fresh(source_path: str) -> bool:
|
|
652
|
+
"""Return True if a co-located .zxc exists and is newer than the source."""
|
|
653
|
+
zxc = zxc_path_for(source_path)
|
|
654
|
+
try:
|
|
655
|
+
if not os.path.exists(zxc):
|
|
656
|
+
return False
|
|
657
|
+
return os.path.getmtime(zxc) >= os.path.getmtime(source_path)
|
|
658
|
+
except OSError:
|
|
659
|
+
return False
|