codecin 5.4.2__py3-none-any.whl
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.
- codecin/__init__.py +27 -0
- codecin/aot.py +229 -0
- codecin/assembler.py +548 -0
- codecin/cache.py +71 -0
- codecin/cin.py +2558 -0
- codecin/cli.py +282 -0
- codecin/config.py +58 -0
- codecin/console.py +183 -0
- codecin/cpu.py +1654 -0
- codecin/crom.py +222 -0
- codecin/debugger.py +659 -0
- codecin/disasm.py +86 -0
- codecin/errors.py +58 -0
- codecin/isa.py +457 -0
- codecin/jit.py +270 -0
- codecin/libcodecin_native.so +0 -0
- codecin/logger.py +145 -0
- codecin/memory.py +348 -0
- codecin/native/aot/aot.go +70 -0
- codecin/native/aot/build.go +219 -0
- codecin/native/aot/stub_main.go.txt +22 -0
- codecin/native/build.ps1 +33 -0
- codecin/native/build.sh +48 -0
- codecin/native/cmd/codecin/main.go +287 -0
- codecin/native/compiler/codegen.go +1938 -0
- codecin/native/compiler/compile_test.go +164 -0
- codecin/native/compiler/parser.go +1071 -0
- codecin/native/compiler/syscalls.go +86 -0
- codecin/native/compiler/tokenizer.go +336 -0
- codecin/native/compiler/types.go +213 -0
- codecin/native/engine/audio.go +152 -0
- codecin/native/engine/audio_other.go +46 -0
- codecin/native/engine/audio_windows.go +43 -0
- codecin/native/engine/canvas.go +271 -0
- codecin/native/engine/crom.go +86 -0
- codecin/native/engine/encode.go +93 -0
- codecin/native/engine/engine_test.go +198 -0
- codecin/native/engine/isa_gen.go +445 -0
- codecin/native/engine/system.go +190 -0
- codecin/native/engine/termux.go +113 -0
- codecin/native/engine/version_gen.go +7 -0
- codecin/native/engine/vm.go +1157 -0
- codecin/native/go.mod +3 -0
- codecin/native/ir/ir.go +48 -0
- codecin/native/main.go +225 -0
- codecin/native.py +336 -0
- codecin/registers.py +130 -0
- codecin/stats.py +235 -0
- codecin-5.4.2.dist-info/METADATA +985 -0
- codecin-5.4.2.dist-info/RECORD +54 -0
- codecin-5.4.2.dist-info/WHEEL +5 -0
- codecin-5.4.2.dist-info/entry_points.txt +2 -0
- codecin-5.4.2.dist-info/licenses/LICENSE +7 -0
- codecin-5.4.2.dist-info/top_level.txt +1 -0
codecin/memory.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from typing import Dict, Iterator, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from .console import Console, Table
|
|
5
|
+
from .errors import MemoryAccessError, PageFaultError
|
|
6
|
+
|
|
7
|
+
# ==================== MMU / 分页 (B1) ====================
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Mmu:
|
|
11
|
+
"""扁平 4K 页表: vpn -> (ppn, perms)。
|
|
12
|
+
|
|
13
|
+
- 默认按需 identity 映射 (vpn == ppn, perms='rwx');
|
|
14
|
+
- unmap 后访问触发 PageFaultError; 权限位按 'r'/'w'/'x' 检查。
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
PAGE_BITS = 12
|
|
18
|
+
PAGE_SIZE = 1 << PAGE_BITS
|
|
19
|
+
PAGE_MASK = PAGE_SIZE - 1
|
|
20
|
+
|
|
21
|
+
def __init__(self, mem_size: int):
|
|
22
|
+
self.mem_size = mem_size
|
|
23
|
+
self.pages: Dict[int, Tuple[int, str]] = {}
|
|
24
|
+
self._blacklist = set() # 显式 unmap 的 vpn (禁止 identity 回填)
|
|
25
|
+
self._identity = True # 未显式配置/未 unmap 的页按 identity 映射
|
|
26
|
+
|
|
27
|
+
def _page_count(self) -> int:
|
|
28
|
+
return (self.mem_size + self.PAGE_SIZE - 1) // self.PAGE_SIZE
|
|
29
|
+
|
|
30
|
+
def reset(self, mem_size: Optional[int] = None) -> None:
|
|
31
|
+
if mem_size is not None:
|
|
32
|
+
self.mem_size = mem_size
|
|
33
|
+
self.pages = {}
|
|
34
|
+
self._blacklist = set()
|
|
35
|
+
|
|
36
|
+
def map(self, vpn: int, ppn: Optional[int] = None,
|
|
37
|
+
perms: str = 'rwx') -> None:
|
|
38
|
+
"""映射 vpn; 缺省 ppn 采用 identity (ppn = vpn)。"""
|
|
39
|
+
if ppn is None:
|
|
40
|
+
ppn = vpn
|
|
41
|
+
self.pages[vpn] = (ppn, perms)
|
|
42
|
+
self._blacklist.discard(vpn)
|
|
43
|
+
|
|
44
|
+
def map_page(self, vaddr: int, paddr: int, perms: str = 'rwx') -> None:
|
|
45
|
+
self.map(vaddr >> self.PAGE_BITS, paddr >> self.PAGE_BITS, perms)
|
|
46
|
+
|
|
47
|
+
def unmap(self, vaddr: int) -> bool:
|
|
48
|
+
vpn = vaddr >> self.PAGE_BITS
|
|
49
|
+
self._blacklist.add(vpn)
|
|
50
|
+
return self.pages.pop(vpn, None) is not None
|
|
51
|
+
|
|
52
|
+
def protect(self, vaddr: int, perms: str) -> None:
|
|
53
|
+
vpn = vaddr >> self.PAGE_BITS
|
|
54
|
+
if vpn in self.pages:
|
|
55
|
+
ppn, _ = self.pages[vpn]
|
|
56
|
+
elif vpn in self._blacklist:
|
|
57
|
+
raise MemoryAccessError(
|
|
58
|
+
f"Cannot protect unmapped page {vpn} (0x{vaddr:x})")
|
|
59
|
+
else:
|
|
60
|
+
ppn = vpn
|
|
61
|
+
self.pages[vpn] = (ppn, perms)
|
|
62
|
+
self._blacklist.discard(vpn)
|
|
63
|
+
|
|
64
|
+
def is_mapped(self, vaddr: int) -> bool:
|
|
65
|
+
vpn = vaddr >> self.PAGE_BITS
|
|
66
|
+
return vpn in self.pages or (self._identity and vpn not in self._blacklist)
|
|
67
|
+
|
|
68
|
+
def translate(self, vaddr: int, access: str) -> int:
|
|
69
|
+
vpn = vaddr >> self.PAGE_BITS
|
|
70
|
+
off = vaddr & self.PAGE_MASK
|
|
71
|
+
ent = self.pages.get(vpn)
|
|
72
|
+
if ent is None:
|
|
73
|
+
if self._identity and vpn not in self._blacklist:
|
|
74
|
+
ent = (vpn, 'rwx') # 懒 identity
|
|
75
|
+
self.pages[vpn] = ent
|
|
76
|
+
else:
|
|
77
|
+
raise PageFaultError(
|
|
78
|
+
f"Page fault at virtual 0x{vaddr:x} (page {vpn} unmapped)")
|
|
79
|
+
ppn, perms = ent
|
|
80
|
+
if access not in perms:
|
|
81
|
+
raise MemoryAccessError(
|
|
82
|
+
f"Page protection violation at 0x{vaddr:x} for '{access}'")
|
|
83
|
+
paddr = (ppn << self.PAGE_BITS) | off
|
|
84
|
+
if not 0 <= paddr < self.mem_size:
|
|
85
|
+
raise PageFaultError(
|
|
86
|
+
f"Physical address 0x{paddr:x} out of memory (size "
|
|
87
|
+
f"0x{self.mem_size:x})")
|
|
88
|
+
return paddr
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ==================== 内存 ====================
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class FastMemory:
|
|
95
|
+
def __init__(self, size: int = 64 * 1024):
|
|
96
|
+
self._size = size
|
|
97
|
+
self._memory = bytearray(size)
|
|
98
|
+
self._view = memoryview(self._memory)
|
|
99
|
+
self._protection: Dict[int, str] = {}
|
|
100
|
+
# B1: 可选 MMU (None = 关)
|
|
101
|
+
self._mmu: Optional[Mmu] = None
|
|
102
|
+
# 调试日志钩子 (DEBUG 级别记录每次读写)
|
|
103
|
+
self._log = None
|
|
104
|
+
|
|
105
|
+
# ---------------- MMU ----------------
|
|
106
|
+
|
|
107
|
+
def attach_mmu(self, mmu: Optional[Mmu]) -> None:
|
|
108
|
+
self._mmu = mmu
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def mmu(self) -> Optional[Mmu]:
|
|
112
|
+
return self._mmu
|
|
113
|
+
|
|
114
|
+
def _phys(self, addr: int, access: str) -> int:
|
|
115
|
+
if self._mmu is None:
|
|
116
|
+
return addr
|
|
117
|
+
return self._mmu.translate(addr, access)
|
|
118
|
+
|
|
119
|
+
def _chunks(self, addr: int, size: int, access: str
|
|
120
|
+
) -> Iterator[Tuple[int, int]]:
|
|
121
|
+
"""把 [addr, addr+size) 翻译为物理分块 (含 MMU 分页)。"""
|
|
122
|
+
if self._mmu is None:
|
|
123
|
+
yield addr, size
|
|
124
|
+
return
|
|
125
|
+
end = addr + size
|
|
126
|
+
while addr < end:
|
|
127
|
+
paddr = self._mmu.translate(addr, access)
|
|
128
|
+
step = min(self._mmu.PAGE_SIZE - (addr & self._mmu.PAGE_MASK),
|
|
129
|
+
end - addr)
|
|
130
|
+
yield paddr, step
|
|
131
|
+
addr += step
|
|
132
|
+
|
|
133
|
+
def attach_logger(self, logger) -> None:
|
|
134
|
+
"""挂接日志器, DEBUG 级别下记录所有内存读写。"""
|
|
135
|
+
self._log = logger
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def _mem_trace(self) -> bool:
|
|
139
|
+
return self._log is not None and self._log.is_debug
|
|
140
|
+
|
|
141
|
+
def _trace_mem(self, op: str, addr: int, value, width: int) -> None:
|
|
142
|
+
vs = f"0x{value:x}" if isinstance(value, int) else repr(value)
|
|
143
|
+
self._log.trace(f" MEM {op:<5} @0x{addr:04x} w={width} value={vs}")
|
|
144
|
+
|
|
145
|
+
def __len__(self) -> int:
|
|
146
|
+
return self._size
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def size(self) -> int:
|
|
150
|
+
return self._size
|
|
151
|
+
|
|
152
|
+
def reset(self, size: Optional[int] = None) -> None:
|
|
153
|
+
if size is not None:
|
|
154
|
+
self._size = size
|
|
155
|
+
self._memory = bytearray(self._size)
|
|
156
|
+
self._view = memoryview(self._memory)
|
|
157
|
+
self._protection.clear()
|
|
158
|
+
if self._mmu is not None:
|
|
159
|
+
self._mmu.reset(self._size)
|
|
160
|
+
|
|
161
|
+
def resize(self, new_size: int) -> None:
|
|
162
|
+
if new_size <= self._size:
|
|
163
|
+
return
|
|
164
|
+
new_mem = bytearray(new_size)
|
|
165
|
+
new_mem[:self._size] = self._memory
|
|
166
|
+
self._memory = new_mem
|
|
167
|
+
self._view = memoryview(self._memory)
|
|
168
|
+
self._size = new_size
|
|
169
|
+
if self._mmu is not None:
|
|
170
|
+
self._mmu.reset(self._size)
|
|
171
|
+
|
|
172
|
+
def set_protection(self, addr: int, perms: str, size: int = 1) -> None:
|
|
173
|
+
for i in range(size):
|
|
174
|
+
self._protection[addr + i] = perms
|
|
175
|
+
|
|
176
|
+
def check_access(self, addr: int, access: str) -> bool:
|
|
177
|
+
perm = self._protection.get(addr, 'rwx')
|
|
178
|
+
return access in perm
|
|
179
|
+
|
|
180
|
+
def _check_bounds(self, addr: int, size: int = 1) -> None:
|
|
181
|
+
if not 0 <= addr <= self._size - size:
|
|
182
|
+
raise MemoryAccessError(
|
|
183
|
+
f"Address 0x{addr:x} out of bounds (memory size 0x{self._size:x}, "
|
|
184
|
+
f"access width {size})")
|
|
185
|
+
|
|
186
|
+
def _check_protection(self, addr: int, access: str) -> None:
|
|
187
|
+
if not self.check_access(addr, access):
|
|
188
|
+
raise MemoryAccessError(
|
|
189
|
+
f"Memory protection violation at 0x{addr:x} for '{access}'")
|
|
190
|
+
|
|
191
|
+
def _check_protection_range(self, addr: int, size: int, access: str) -> None:
|
|
192
|
+
"""对 [addr, addr+size) 内每个被保护字节做访问检查 (块读写统一入口)。"""
|
|
193
|
+
if not self._protection:
|
|
194
|
+
return
|
|
195
|
+
end = addr + size
|
|
196
|
+
for paddr, perms in self._protection.items():
|
|
197
|
+
if addr <= paddr < end and access not in perms:
|
|
198
|
+
raise MemoryAccessError(
|
|
199
|
+
f"Memory protection violation at 0x{paddr:x} "
|
|
200
|
+
f"for '{access}' (range 0x{addr:x}+{size})")
|
|
201
|
+
|
|
202
|
+
# ---------------- 单字节 ----------------
|
|
203
|
+
|
|
204
|
+
def read_byte(self, addr: int) -> int:
|
|
205
|
+
p = self._phys(addr, 'r')
|
|
206
|
+
self._check_bounds(p)
|
|
207
|
+
self._check_protection(p, 'r')
|
|
208
|
+
v = self._view[p]
|
|
209
|
+
if self._mem_trace:
|
|
210
|
+
self._trace_mem('RD', addr, v, 1)
|
|
211
|
+
return v
|
|
212
|
+
|
|
213
|
+
def write_byte(self, addr: int, value: int) -> None:
|
|
214
|
+
p = self._phys(addr, 'w')
|
|
215
|
+
self._check_bounds(p)
|
|
216
|
+
self._check_protection(p, 'w')
|
|
217
|
+
self._view[p] = value & 0xFF
|
|
218
|
+
if self._mem_trace:
|
|
219
|
+
self._trace_mem('WR', addr, value & 0xFF, 1)
|
|
220
|
+
|
|
221
|
+
# ---------------- 定宽多字节 (要求不跨页) ----------------
|
|
222
|
+
|
|
223
|
+
def _rw_phys(self, addr: int, width: int, access: str) -> int:
|
|
224
|
+
p = self._phys(addr, access)
|
|
225
|
+
self._check_bounds(p, width)
|
|
226
|
+
self._check_protection(p, access)
|
|
227
|
+
return p
|
|
228
|
+
|
|
229
|
+
def read_word(self, addr: int) -> int:
|
|
230
|
+
p = self._rw_phys(addr, 2, 'r')
|
|
231
|
+
v = struct.unpack_from('<H', self._view, p)[0]
|
|
232
|
+
if self._mem_trace:
|
|
233
|
+
self._trace_mem('RD', addr, v, 2)
|
|
234
|
+
return v
|
|
235
|
+
|
|
236
|
+
def write_word(self, addr: int, value: int) -> None:
|
|
237
|
+
p = self._rw_phys(addr, 2, 'w')
|
|
238
|
+
struct.pack_into('<H', self._view, p, value & 0xFFFF)
|
|
239
|
+
if self._mem_trace:
|
|
240
|
+
self._trace_mem('WR', addr, value & 0xFFFF, 2)
|
|
241
|
+
|
|
242
|
+
def read_dword(self, addr: int) -> int:
|
|
243
|
+
p = self._rw_phys(addr, 4, 'r')
|
|
244
|
+
v = struct.unpack_from('<I', self._view, p)[0]
|
|
245
|
+
if self._mem_trace:
|
|
246
|
+
self._trace_mem('RD', addr, v, 4)
|
|
247
|
+
return v
|
|
248
|
+
|
|
249
|
+
def write_dword(self, addr: int, value: int) -> None:
|
|
250
|
+
p = self._rw_phys(addr, 4, 'w')
|
|
251
|
+
struct.pack_into('<I', self._view, p, value & 0xFFFFFFFF)
|
|
252
|
+
if self._mem_trace:
|
|
253
|
+
self._trace_mem('WR', addr, value & 0xFFFFFFFF, 4)
|
|
254
|
+
|
|
255
|
+
def read_qword(self, addr: int) -> int:
|
|
256
|
+
p = self._rw_phys(addr, 8, 'r')
|
|
257
|
+
v = struct.unpack_from('<Q', self._view, p)[0]
|
|
258
|
+
if self._mem_trace:
|
|
259
|
+
self._trace_mem('RD', addr, v, 8)
|
|
260
|
+
return v
|
|
261
|
+
|
|
262
|
+
def write_qword(self, addr: int, value: int) -> None:
|
|
263
|
+
p = self._rw_phys(addr, 8, 'w')
|
|
264
|
+
struct.pack_into('<Q', self._view, p, value & 0xFFFFFFFFFFFFFFFF)
|
|
265
|
+
if self._mem_trace:
|
|
266
|
+
self._trace_mem('WR', addr, value & 0xFFFFFFFFFFFFFFFF, 8)
|
|
267
|
+
|
|
268
|
+
def read_float(self, addr: int) -> float:
|
|
269
|
+
p = self._rw_phys(addr, 4, 'r')
|
|
270
|
+
return struct.unpack_from('<f', self._view, p)[0]
|
|
271
|
+
|
|
272
|
+
def write_float(self, addr: int, value: float) -> None:
|
|
273
|
+
p = self._rw_phys(addr, 4, 'w')
|
|
274
|
+
struct.pack_into('<f', self._view, p, value)
|
|
275
|
+
|
|
276
|
+
def read_double(self, addr: int) -> float:
|
|
277
|
+
p = self._rw_phys(addr, 8, 'r')
|
|
278
|
+
return struct.unpack_from('<d', self._view, p)[0]
|
|
279
|
+
|
|
280
|
+
def write_double(self, addr: int, value: float) -> None:
|
|
281
|
+
p = self._rw_phys(addr, 8, 'w')
|
|
282
|
+
struct.pack_into('<d', self._view, p, value)
|
|
283
|
+
|
|
284
|
+
# ---------------- 块 / 字符串 ----------------
|
|
285
|
+
|
|
286
|
+
def read_block(self, addr: int, size: int) -> bytes:
|
|
287
|
+
self._check_bounds(addr, size)
|
|
288
|
+
self._check_protection_range(addr, size, 'r')
|
|
289
|
+
if self._mmu is None:
|
|
290
|
+
return bytes(self._view[addr:addr + size])
|
|
291
|
+
parts = []
|
|
292
|
+
for p, step in self._chunks(addr, size, 'r'):
|
|
293
|
+
parts.append(bytes(self._view[p:p + step]))
|
|
294
|
+
return b''.join(parts)
|
|
295
|
+
|
|
296
|
+
def write_block(self, addr: int, data) -> None:
|
|
297
|
+
size = len(data)
|
|
298
|
+
self._check_bounds(addr, size)
|
|
299
|
+
self._check_protection_range(addr, size, 'w')
|
|
300
|
+
if self._mmu is None:
|
|
301
|
+
self._view[addr:addr + size] = data
|
|
302
|
+
return
|
|
303
|
+
src = bytes(data)
|
|
304
|
+
off = 0
|
|
305
|
+
for p, step in self._chunks(addr, size, 'w'):
|
|
306
|
+
self._view[p:p + step] = src[off:off + step]
|
|
307
|
+
off += step
|
|
308
|
+
|
|
309
|
+
def read_string(self, addr: int, max_len: int = 4096) -> str:
|
|
310
|
+
chars = []
|
|
311
|
+
for _ in range(max_len):
|
|
312
|
+
b = self.read_byte(addr)
|
|
313
|
+
if b == 0:
|
|
314
|
+
break
|
|
315
|
+
chars.append(b)
|
|
316
|
+
addr += 1
|
|
317
|
+
return bytes(chars).decode('utf-8', errors='replace')
|
|
318
|
+
|
|
319
|
+
def write_string(self, addr: int, text: str) -> int:
|
|
320
|
+
data = text.encode('utf-8') + b'\x00'
|
|
321
|
+
self.write_block(addr, data)
|
|
322
|
+
return len(data)
|
|
323
|
+
|
|
324
|
+
def load_bytes(self, addr: int, data) -> None:
|
|
325
|
+
self.write_block(addr, bytes(data))
|
|
326
|
+
|
|
327
|
+
def get_snapshot(self, start: int = 0, count: int = -1) -> bytes:
|
|
328
|
+
if count < 0:
|
|
329
|
+
count = self._size - start
|
|
330
|
+
return bytes(self._view[start:start + count])
|
|
331
|
+
|
|
332
|
+
def display_memory(self, title: str = "Memory Dump", start: int = 0,
|
|
333
|
+
count: int = 32, console: Optional[Console] = None) -> None:
|
|
334
|
+
if console is None:
|
|
335
|
+
return
|
|
336
|
+
table = Table(title=title)
|
|
337
|
+
table.add_column("Address")
|
|
338
|
+
table.add_column("Hex")
|
|
339
|
+
table.add_column("ASCII")
|
|
340
|
+
table.add_column("Prot")
|
|
341
|
+
end = min(start + count, self._size)
|
|
342
|
+
for i in range(start, end, 16):
|
|
343
|
+
chunk = self._view[i:min(i + 16, end)]
|
|
344
|
+
hex_str = ' '.join(f'{b:02X}' for b in chunk)
|
|
345
|
+
ascii_str = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
|
|
346
|
+
perm = self._protection.get(i, 'rwx')
|
|
347
|
+
table.add_row(f"{i:04X}", hex_str, ascii_str, perm)
|
|
348
|
+
console.print(str(table))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Package aot 提供 AOT 构建: 把 CIN 程序编译成**独立静态可执行文件**。
|
|
2
|
+
//
|
|
3
|
+
// 产物不依赖 Python、不依赖 Go 工具链、不依赖任何动态库:
|
|
4
|
+
// 字节码与初始内存镜像被嵌入二进制, 由内置的 Go VM 执行
|
|
5
|
+
// (构建时 CGO_ENABLED=0, 因此 Windows / Linux / macOS 均为静态链接)。
|
|
6
|
+
//
|
|
7
|
+
// 构建流程 (Build):
|
|
8
|
+
// 1. 在 codecin-native 模块内创建临时包目录 .aotbuild-<rand>/
|
|
9
|
+
// (Go 工具链会忽略以 '.' 开头的目录, 因此不影响 go build ./...);
|
|
10
|
+
// 2. 写入 main.go (由共享模板 stub_main.go.txt 提供) 与两个资源文件;
|
|
11
|
+
// 3. CGO_ENABLED=0 + GOOS/GOARCH 交叉编译出目标平台可执行文件;
|
|
12
|
+
// 4. 删除临时目录。
|
|
13
|
+
package aot
|
|
14
|
+
|
|
15
|
+
import (
|
|
16
|
+
_ "embed"
|
|
17
|
+
"fmt"
|
|
18
|
+
"io"
|
|
19
|
+
"os"
|
|
20
|
+
|
|
21
|
+
"codecin-native/engine"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// StubSource 是生成的 main.go 模板, 与 Python 侧 codecin/aot.py 共用同一份
|
|
25
|
+
// 文件 (codecin/native/aot/stub_main.go.txt), 避免两处模板漂移。
|
|
26
|
+
//
|
|
27
|
+
//go:embed stub_main.go.txt
|
|
28
|
+
var stubSource string
|
|
29
|
+
|
|
30
|
+
// StubSource 返回生成的 main.go 源码。
|
|
31
|
+
func StubSource() string { return stubSource }
|
|
32
|
+
|
|
33
|
+
const maxSteps = 100000000
|
|
34
|
+
|
|
35
|
+
// stackSlot 与解释器/CLI 一致: 栈从内存末尾向下增长。
|
|
36
|
+
const stackSlot = 8
|
|
37
|
+
|
|
38
|
+
// Main 运行内嵌的字节码并返回进程退出码 (0 = 正常结束, 1 = 运行期错误)。
|
|
39
|
+
//
|
|
40
|
+
// 标准输入只在被重定向 (管道/文件) 时读取, 交互式终端下不会挂起等待 EOF。
|
|
41
|
+
func Main(bytecode, memImage []byte) int {
|
|
42
|
+
mem := make([]byte, len(memImage))
|
|
43
|
+
copy(mem, memImage)
|
|
44
|
+
|
|
45
|
+
var in []byte
|
|
46
|
+
if fi, err := os.Stdin.Stat(); err == nil &&
|
|
47
|
+
fi.Mode()&os.ModeCharDevice == 0 {
|
|
48
|
+
if data, err := io.ReadAll(os.Stdin); err == nil {
|
|
49
|
+
in = data
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
sp := int64(len(mem) - stackSlot)
|
|
54
|
+
res := engine.Run(bytecode, mem, 0, sp, int64(len(mem)/2), in, maxSteps)
|
|
55
|
+
if res == nil {
|
|
56
|
+
fmt.Fprintln(os.Stderr, "runtime error: no result")
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
fmt.Print(res.Output)
|
|
60
|
+
if res.Status == engine.StatusError ||
|
|
61
|
+
res.Status == engine.StatusUnsupported {
|
|
62
|
+
msg := res.ErrMsg
|
|
63
|
+
if msg == "" {
|
|
64
|
+
msg = "unsupported instruction (原生 VM 未实现该指令)"
|
|
65
|
+
}
|
|
66
|
+
fmt.Fprintf(os.Stderr, "runtime error: %s\n", msg)
|
|
67
|
+
return 1
|
|
68
|
+
}
|
|
69
|
+
return 0
|
|
70
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
package aot
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"crypto/rand"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"os"
|
|
9
|
+
"os/exec"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"runtime"
|
|
12
|
+
"strings"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// DefaultMemSize 与解释器 / Go CLI 保持一致 (64 KiB)。
|
|
16
|
+
const DefaultMemSize = 65536
|
|
17
|
+
|
|
18
|
+
// Target 是构建目标平台。
|
|
19
|
+
type Target struct {
|
|
20
|
+
GOOS string
|
|
21
|
+
GOARCH string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func (t Target) String() string { return t.GOOS + "/" + t.GOARCH }
|
|
25
|
+
|
|
26
|
+
// ExeSuffix 返回该平台可执行文件的后缀。
|
|
27
|
+
func (t Target) ExeSuffix() string {
|
|
28
|
+
if t.GOOS == "windows" {
|
|
29
|
+
return ".exe"
|
|
30
|
+
}
|
|
31
|
+
return ""
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// SupportedTargets 是文档/CLI 帮助里列出的常用目标。
|
|
35
|
+
var SupportedTargets = []string{
|
|
36
|
+
"windows/amd64", "windows/arm64",
|
|
37
|
+
"linux/amd64", "linux/arm64",
|
|
38
|
+
"darwin/amd64", "darwin/arm64",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// BuildOptions 描述一次 AOT 构建。
|
|
42
|
+
type BuildOptions struct {
|
|
43
|
+
// ModuleDir 是含 `module codecin-native` 的 go.mod 的目录。
|
|
44
|
+
ModuleDir string
|
|
45
|
+
Bytecode []byte
|
|
46
|
+
MemImage []byte
|
|
47
|
+
// Out 是输出可执行文件路径 (相对当前工作目录)。
|
|
48
|
+
Out string
|
|
49
|
+
Target Target
|
|
50
|
+
// KeepTemp 为真时保留临时构建目录 (排查构建失败用)。
|
|
51
|
+
KeepTemp bool
|
|
52
|
+
Stdout io.Writer
|
|
53
|
+
Stderr io.Writer
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Build 生成独立静态可执行文件, 返回产物的绝对路径。
|
|
57
|
+
//
|
|
58
|
+
// 静态性来自 CGO_ENABLED=0: Windows / Linux / macOS 三个平台都不再依赖
|
|
59
|
+
// libc 或任何动态库, 也不需要 Python 与 Go 运行时。
|
|
60
|
+
func Build(opts BuildOptions) (string, error) {
|
|
61
|
+
if opts.ModuleDir == "" {
|
|
62
|
+
return "", fmt.Errorf("未指定 Go 模块目录 (含 codecin-native/go.mod 的目录)")
|
|
63
|
+
}
|
|
64
|
+
if opts.Out == "" {
|
|
65
|
+
return "", fmt.Errorf("未指定输出路径")
|
|
66
|
+
}
|
|
67
|
+
if opts.Target.GOOS == "" {
|
|
68
|
+
opts.Target.GOOS = runtime.GOOS
|
|
69
|
+
}
|
|
70
|
+
if opts.Target.GOARCH == "" {
|
|
71
|
+
opts.Target.GOARCH = runtime.GOARCH
|
|
72
|
+
}
|
|
73
|
+
if len(opts.Bytecode) == 0 {
|
|
74
|
+
return "", fmt.Errorf("字节码为空")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 临时包放在模块内并以 '.' 开头: Go 工具链会忽略这类目录,
|
|
78
|
+
// 因此既不影响 `go build ./...`, 也无需 replace/绝对路径。
|
|
79
|
+
//
|
|
80
|
+
// 注意: 这里用 os.Mkdir(0755) 而不是 os.MkdirTemp —— MkdirTemp 会建 0700
|
|
81
|
+
// 目录, 在受限环境 (沙箱 / 部分 CI 安全策略) 下随后向其中写文件会被拒绝。
|
|
82
|
+
tmp, err := newBuildDir(opts.ModuleDir)
|
|
83
|
+
if err != nil {
|
|
84
|
+
return "", fmt.Errorf("创建临时构建目录失败 (模块目录可写?): %w", err)
|
|
85
|
+
}
|
|
86
|
+
if !opts.KeepTemp {
|
|
87
|
+
defer func() { _ = os.RemoveAll(tmp) }()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
write := func(name string, data []byte) error {
|
|
91
|
+
return os.WriteFile(filepath.Join(tmp, name), data, 0o644)
|
|
92
|
+
}
|
|
93
|
+
if err := write("main.go", []byte(stubSource)); err != nil {
|
|
94
|
+
return "", fmt.Errorf("写入 main.go 失败: %w", err)
|
|
95
|
+
}
|
|
96
|
+
if err := write("program.ucbc", opts.Bytecode); err != nil {
|
|
97
|
+
return "", fmt.Errorf("写入 program.ucbc 失败: %w", err)
|
|
98
|
+
}
|
|
99
|
+
if err := write("program.mem", opts.MemImage); err != nil {
|
|
100
|
+
return "", fmt.Errorf("写入 program.mem 失败: %w", err)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
outAbs, err := filepath.Abs(opts.Out)
|
|
104
|
+
if err != nil {
|
|
105
|
+
return "", fmt.Errorf("解析输出路径失败: %w", err)
|
|
106
|
+
}
|
|
107
|
+
if dir := filepath.Dir(outAbs); dir != "" {
|
|
108
|
+
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
109
|
+
return "", fmt.Errorf("创建输出目录失败: %w", err)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
pkg := "." + string(filepath.Separator) + filepath.Base(tmp)
|
|
114
|
+
cmd := exec.Command("go", "build",
|
|
115
|
+
"-trimpath",
|
|
116
|
+
"-tags", "netgo,osusergo", // 纯 Go 网络/用户实现, 保证无 libc 依赖
|
|
117
|
+
"-ldflags", "-s -w", // 去掉符号表与调试信息
|
|
118
|
+
"-o", outAbs, pkg)
|
|
119
|
+
cmd.Dir = opts.ModuleDir
|
|
120
|
+
cmd.Stdout = opts.Stdout
|
|
121
|
+
stderr := &bytes.Buffer{}
|
|
122
|
+
if opts.Stderr != nil {
|
|
123
|
+
cmd.Stderr = io.MultiWriter(opts.Stderr, stderr)
|
|
124
|
+
} else {
|
|
125
|
+
cmd.Stderr = stderr
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
env := append(os.Environ(),
|
|
129
|
+
"CGO_ENABLED=0", // 关键: 静态链接
|
|
130
|
+
"GOOS="+opts.Target.GOOS,
|
|
131
|
+
"GOARCH="+opts.Target.GOARCH,
|
|
132
|
+
)
|
|
133
|
+
cmd.Env = env
|
|
134
|
+
err = cmd.Run()
|
|
135
|
+
|
|
136
|
+
// 受限环境 (沙箱 / 只读 HOME / 部分 CI) 下 Go 默认构建缓存可能不可写,
|
|
137
|
+
// 直接失败会给出难以理解的 "Access is denied"; 此时回退到仓库内 .gocache 重试一次。
|
|
138
|
+
if err != nil && cacheWriteDenied(stderr.String()) && os.Getenv("GOCACHE") == "" {
|
|
139
|
+
cache := fallbackCacheDir(opts.ModuleDir)
|
|
140
|
+
if mkErr := os.MkdirAll(cache, 0o755); mkErr == nil {
|
|
141
|
+
retry := exec.Command("go", cmd.Args[1:]...)
|
|
142
|
+
retry.Dir = opts.ModuleDir
|
|
143
|
+
retry.Stdout = opts.Stdout
|
|
144
|
+
retry.Stderr = cmd.Stderr
|
|
145
|
+
retry.Env = append(env, "GOCACHE="+cache)
|
|
146
|
+
if retryErr := retry.Run(); retryErr == nil {
|
|
147
|
+
return outAbs, nil
|
|
148
|
+
} else {
|
|
149
|
+
err = fmt.Errorf("%w (已回退缓存目录 %s 仍失败)", retryErr, cache)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else if err != nil && cacheWriteDenied(stderr.String()) {
|
|
153
|
+
err = fmt.Errorf("%w (提示: 构建缓存不可写, 可用 GOCACHE 指向可写目录)", err)
|
|
154
|
+
}
|
|
155
|
+
if err != nil {
|
|
156
|
+
return "", fmt.Errorf("go build 失败 (目标 %s): %w", opts.Target, err)
|
|
157
|
+
}
|
|
158
|
+
return outAbs, nil
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// fallbackCacheDir 返回仓库内的构建缓存目录 (install 脚本同款, 已被 .gitignore 忽略)。
|
|
162
|
+
func fallbackCacheDir(moduleDir string) string {
|
|
163
|
+
root := filepath.Dir(filepath.Dir(filepath.Clean(moduleDir)))
|
|
164
|
+
return filepath.Join(root, ".gocache")
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// cacheWriteDenied 判断 go build 的报错是否属于"构建缓存不可写"。
|
|
168
|
+
func cacheWriteDenied(text string) bool {
|
|
169
|
+
low := strings.ToLower(text)
|
|
170
|
+
if !strings.Contains(low, "cache") && !strings.Contains(low, "go-build") {
|
|
171
|
+
return false
|
|
172
|
+
}
|
|
173
|
+
return strings.Contains(low, "access is denied") ||
|
|
174
|
+
strings.Contains(low, "permission denied") ||
|
|
175
|
+
strings.Contains(low, "operation not permitted")
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// newBuildDir 在模块内创建一个 0755 的临时包目录。
|
|
179
|
+
// 不用 os.MkdirTemp: 它建的是 0700 目录, 受限环境下随后写入会被拒绝。
|
|
180
|
+
func newBuildDir(moduleDir string) (string, error) {
|
|
181
|
+
var buf [6]byte
|
|
182
|
+
if _, err := rand.Read(buf[:]); err != nil {
|
|
183
|
+
return "", err
|
|
184
|
+
}
|
|
185
|
+
path := filepath.Join(moduleDir, fmt.Sprintf(".aotbuild-%x", buf))
|
|
186
|
+
if err := os.Mkdir(path, 0o755); err != nil {
|
|
187
|
+
return "", err
|
|
188
|
+
}
|
|
189
|
+
return path, nil
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// FindModuleDir 从给定的若干起点向上查找 `module codecin-native` 所在目录。
|
|
193
|
+
func FindModuleDir(starts ...string) string {
|
|
194
|
+
for _, start := range starts {
|
|
195
|
+
if start == "" {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
dir, err := filepath.Abs(start)
|
|
199
|
+
if err != nil {
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
if fi, err := os.Stat(dir); err == nil && !fi.IsDir() {
|
|
203
|
+
dir = filepath.Dir(dir)
|
|
204
|
+
}
|
|
205
|
+
for {
|
|
206
|
+
gomod := filepath.Join(dir, "go.mod")
|
|
207
|
+
if data, err := os.ReadFile(gomod); err == nil &&
|
|
208
|
+
strings.Contains(string(data), "module codecin-native") {
|
|
209
|
+
return dir
|
|
210
|
+
}
|
|
211
|
+
parent := filepath.Dir(dir)
|
|
212
|
+
if parent == dir {
|
|
213
|
+
break
|
|
214
|
+
}
|
|
215
|
+
dir = parent
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return ""
|
|
219
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Code generated by the Code CIN AOT builder; DO NOT EDIT.
|
|
2
|
+
//
|
|
3
|
+
// 这是独立可执行文件的入口 shell: program.ucbc 与 program.mem 由构建器
|
|
4
|
+
// 生成并嵌入, 运行时由内置 Go VM 执行 (静态链接, 无需 Python/动态库)。
|
|
5
|
+
package main
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
_ "embed"
|
|
9
|
+
"os"
|
|
10
|
+
|
|
11
|
+
"codecin-native/aot"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
//go:embed program.ucbc
|
|
15
|
+
var bytecode []byte
|
|
16
|
+
|
|
17
|
+
//go:embed program.mem
|
|
18
|
+
var memImage []byte
|
|
19
|
+
|
|
20
|
+
func main() {
|
|
21
|
+
os.Exit(aot.Main(bytecode, memImage))
|
|
22
|
+
}
|
codecin/native/build.ps1
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Code CIN 原生库构建脚本 (Windows)
|
|
2
|
+
# 依赖: Go 1.26+ 且启用 cgo (需 C 编译器, 如 MinGW-w64 / TDM-GCC 的 gcc / clang)
|
|
3
|
+
# 用法: 在本目录执行 .\build.ps1
|
|
4
|
+
# 静态链接: $env:CODECIN_STATIC = '1' (默认尝试静态, 失败自动回退动态)
|
|
5
|
+
# 动态链接: $env:CODECIN_STATIC = '0'
|
|
6
|
+
param()
|
|
7
|
+
$ErrorActionPreference = 'Stop'
|
|
8
|
+
$dir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
9
|
+
Set-Location $dir
|
|
10
|
+
$env:CGO_ENABLED = '1'
|
|
11
|
+
|
|
12
|
+
$out = Join-Path $dir '..\codecin_native.dll'
|
|
13
|
+
$mode = if ($env:CODECIN_STATIC) { $env:CODECIN_STATIC } else { 'auto' }
|
|
14
|
+
|
|
15
|
+
$staticLd = '-linkmode external -extldflags -static'
|
|
16
|
+
$built = $false
|
|
17
|
+
|
|
18
|
+
if ($mode -ne '0') {
|
|
19
|
+
Write-Host "Building (static linking)..."
|
|
20
|
+
go build -buildmode=c-shared -ldflags $staticLd -o $out .
|
|
21
|
+
if ($LASTEXITCODE -eq 0) { $built = $true }
|
|
22
|
+
elseif ($mode -eq '1') { throw "static go build failed (exit $LASTEXITCODE)" }
|
|
23
|
+
else { Write-Host "static linking unavailable, falling back to dynamic" -ForegroundColor Yellow }
|
|
24
|
+
}
|
|
25
|
+
if (-not $built) {
|
|
26
|
+
Write-Host "Building (dynamic linking)..."
|
|
27
|
+
go build -buildmode=c-shared -o $out .
|
|
28
|
+
if ($LASTEXITCODE -ne 0) { throw "go build failed (exit $LASTEXITCODE)" }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# c-shared 会附带生成头文件, Python ctypes 不需要
|
|
32
|
+
Remove-Item -Force (Join-Path $dir '..\codecin_native.h') -ErrorAction SilentlyContinue
|
|
33
|
+
Write-Host ("Built: " + (Resolve-Path $out).Path)
|