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.
Files changed (54) hide show
  1. codecin/__init__.py +27 -0
  2. codecin/aot.py +229 -0
  3. codecin/assembler.py +548 -0
  4. codecin/cache.py +71 -0
  5. codecin/cin.py +2558 -0
  6. codecin/cli.py +282 -0
  7. codecin/config.py +58 -0
  8. codecin/console.py +183 -0
  9. codecin/cpu.py +1654 -0
  10. codecin/crom.py +222 -0
  11. codecin/debugger.py +659 -0
  12. codecin/disasm.py +86 -0
  13. codecin/errors.py +58 -0
  14. codecin/isa.py +457 -0
  15. codecin/jit.py +270 -0
  16. codecin/libcodecin_native.so +0 -0
  17. codecin/logger.py +145 -0
  18. codecin/memory.py +348 -0
  19. codecin/native/aot/aot.go +70 -0
  20. codecin/native/aot/build.go +219 -0
  21. codecin/native/aot/stub_main.go.txt +22 -0
  22. codecin/native/build.ps1 +33 -0
  23. codecin/native/build.sh +48 -0
  24. codecin/native/cmd/codecin/main.go +287 -0
  25. codecin/native/compiler/codegen.go +1938 -0
  26. codecin/native/compiler/compile_test.go +164 -0
  27. codecin/native/compiler/parser.go +1071 -0
  28. codecin/native/compiler/syscalls.go +86 -0
  29. codecin/native/compiler/tokenizer.go +336 -0
  30. codecin/native/compiler/types.go +213 -0
  31. codecin/native/engine/audio.go +152 -0
  32. codecin/native/engine/audio_other.go +46 -0
  33. codecin/native/engine/audio_windows.go +43 -0
  34. codecin/native/engine/canvas.go +271 -0
  35. codecin/native/engine/crom.go +86 -0
  36. codecin/native/engine/encode.go +93 -0
  37. codecin/native/engine/engine_test.go +198 -0
  38. codecin/native/engine/isa_gen.go +445 -0
  39. codecin/native/engine/system.go +190 -0
  40. codecin/native/engine/termux.go +113 -0
  41. codecin/native/engine/version_gen.go +7 -0
  42. codecin/native/engine/vm.go +1157 -0
  43. codecin/native/go.mod +3 -0
  44. codecin/native/ir/ir.go +48 -0
  45. codecin/native/main.go +225 -0
  46. codecin/native.py +336 -0
  47. codecin/registers.py +130 -0
  48. codecin/stats.py +235 -0
  49. codecin-5.4.2.dist-info/METADATA +985 -0
  50. codecin-5.4.2.dist-info/RECORD +54 -0
  51. codecin-5.4.2.dist-info/WHEEL +5 -0
  52. codecin-5.4.2.dist-info/entry_points.txt +2 -0
  53. codecin-5.4.2.dist-info/licenses/LICENSE +7 -0
  54. 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
+ }
@@ -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)