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
@@ -0,0 +1,152 @@
1
+ package engine
2
+
3
+ import (
4
+ "encoding/binary"
5
+ "fmt"
6
+ "io"
7
+ "net/http"
8
+ "os"
9
+ "os/exec"
10
+ "strings"
11
+ "time"
12
+ )
13
+
14
+ // 联网音频: 下载 (http/https) 或本地文件 → 解析 WAV → 平台播放。
15
+ // 支持格式: WAV (PCM)。MP3/OGG 需外部解码器, 本实现不包含。
16
+ //
17
+ // 平台播放后端由构建约束拆分:
18
+ // audio_windows.go (//go:build windows) —— winmm PlaySoundW
19
+ // audio_other.go (//go:build !windows) —— afplay / aplay / paplay / ffplay
20
+
21
+ // 宿主全局音频状态 (同一时刻一个音频流)。
22
+ var (
23
+ audioActive bool
24
+ audioDur time.Duration
25
+ audioEnd time.Time
26
+ audioTemp string // 当前播放的临时 WAV 文件路径 (异步播放时保留)
27
+ audioCmd *exec.Cmd
28
+ audioStarted time.Time
29
+ )
30
+
31
+ // MaxResourceBytes 是 FetchResource 的下载/读取上限 (音频等宿主资源)。
32
+ const MaxResourceBytes = 64 << 20 // 64 MiB
33
+
34
+ // FetchResource 下载 URL 或读取本地文件, 返回原始字节。
35
+ func FetchResource(loc string) ([]byte, error) {
36
+ if strings.HasPrefix(loc, "http://") || strings.HasPrefix(loc, "https://") {
37
+ client := &http.Client{Timeout: 30 * time.Second}
38
+ resp, err := client.Get(loc)
39
+ if err != nil {
40
+ return nil, err
41
+ }
42
+ defer resp.Body.Close()
43
+ if resp.StatusCode != http.StatusOK {
44
+ return nil, fmt.Errorf("http status %d", resp.StatusCode)
45
+ }
46
+ // 有上限读取: Content-Length 与实际读取量双重限制,
47
+ // 防止恶意/异常 URL 用无限流把宿主 OOM。
48
+ if resp.ContentLength > MaxResourceBytes {
49
+ return nil, fmt.Errorf("resource too large: %d bytes", resp.ContentLength)
50
+ }
51
+ data, err := io.ReadAll(io.LimitReader(resp.Body, MaxResourceBytes+1))
52
+ if err != nil {
53
+ return nil, err
54
+ }
55
+ if len(data) > MaxResourceBytes {
56
+ return nil, fmt.Errorf("resource too large: >%d bytes", MaxResourceBytes)
57
+ }
58
+ return data, nil
59
+ }
60
+ fi, err := os.Stat(loc)
61
+ if err == nil && fi.Size() > MaxResourceBytes {
62
+ return nil, fmt.Errorf("resource too large: %d bytes", fi.Size())
63
+ }
64
+ return os.ReadFile(loc)
65
+ }
66
+
67
+ // WavDuration 解析 WAV 头并返回时长 (0 表示无法识别)。
68
+ func WavDuration(data []byte) time.Duration {
69
+ if len(data) < 44 || string(data[0:4]) != "RIFF" || string(data[8:12]) != "WAVE" {
70
+ return 0
71
+ }
72
+ byteRate := binary.LittleEndian.Uint32(data[28:32])
73
+ dataSize := binary.LittleEndian.Uint32(data[40:44])
74
+ if byteRate == 0 {
75
+ return 0
76
+ }
77
+ return time.Duration(float64(dataSize) / float64(byteRate) * float64(time.Second))
78
+ }
79
+
80
+ // isWav 判断是否为 WAV (RIFF/WAVE)。
81
+ func isWav(data []byte) bool {
82
+ return len(data) >= 12 && string(data[0:4]) == "RIFF" && string(data[8:12]) == "WAVE"
83
+ }
84
+
85
+ // writeTempWav 将 WAV 字节写入临时文件并记录路径。
86
+ func writeTempWav(data []byte) (string, error) {
87
+ f, err := os.CreateTemp("", "codecin_*.wav")
88
+ if err != nil {
89
+ return "", err
90
+ }
91
+ if _, err := f.Write(data); err != nil {
92
+ _ = f.Close()
93
+ _ = os.Remove(f.Name())
94
+ return "", err
95
+ }
96
+ if err := f.Close(); err != nil {
97
+ _ = os.Remove(f.Name())
98
+ return "", err
99
+ }
100
+ audioTemp = f.Name()
101
+ return audioTemp, nil
102
+ }
103
+
104
+ // cleanupTempWav 删除临时 WAV 文件。
105
+ func cleanupTempWav() {
106
+ if audioTemp != "" {
107
+ _ = os.Remove(audioTemp)
108
+ audioTemp = ""
109
+ }
110
+ }
111
+
112
+ func (vm *vmState) audioPlay(loc string) uint64 {
113
+ data, err := FetchResource(loc)
114
+ if err != nil {
115
+ return mask64 // -1
116
+ }
117
+ if !isWav(data) {
118
+ return mask64 // 不支持的格式
119
+ }
120
+ if err := playPlatform(data); err != nil {
121
+ return mask64
122
+ }
123
+ audioActive = true
124
+ audioDur = WavDuration(data)
125
+ audioStarted = time.Now()
126
+ audioEnd = audioStarted.Add(audioDur)
127
+ return 0
128
+ }
129
+
130
+ func (vm *vmState) audioStop() {
131
+ audioActive = false
132
+ stopPlatform()
133
+ }
134
+
135
+ func (vm *vmState) audioVolume(level uint64) {
136
+ // 音量控制依赖 OS 混音器, 本实现忽略 (保留参数以便后续扩展)。
137
+ _ = level
138
+ }
139
+
140
+ func (vm *vmState) audioWait() {
141
+ if !audioActive {
142
+ return
143
+ }
144
+ if audioDur <= 0 {
145
+ return
146
+ }
147
+ remain := time.Until(audioEnd)
148
+ if remain > 0 {
149
+ time.Sleep(remain)
150
+ }
151
+ audioActive = false
152
+ }
@@ -0,0 +1,46 @@
1
+ //go:build !windows
2
+
3
+ package engine
4
+
5
+ import (
6
+ "fmt"
7
+ "os/exec"
8
+ "runtime"
9
+ )
10
+
11
+ // Unix 音频后端 (Linux / macOS / Termux / BSD): 调用系统播放器。
12
+ // macOS: afplay
13
+ // Linux/Termux: aplay / paplay / ffplay (取第一个可用者)
14
+
15
+ func playPlatform(data []byte) error {
16
+ path, err := writeTempWav(data)
17
+ if err != nil {
18
+ return err
19
+ }
20
+
21
+ if runtime.GOOS == "darwin" {
22
+ audioCmd = exec.Command("afplay", path)
23
+ return audioCmd.Start()
24
+ }
25
+
26
+ for _, p := range []string{"aplay", "paplay", "ffplay"} {
27
+ if _, err := exec.LookPath(p); err != nil {
28
+ continue
29
+ }
30
+ if p == "ffplay" {
31
+ audioCmd = exec.Command(p, "-nodisp", "-autoexit", "-loglevel", "quiet", path)
32
+ } else {
33
+ audioCmd = exec.Command(p, path)
34
+ }
35
+ return audioCmd.Start()
36
+ }
37
+ return fmt.Errorf("no audio player found (aplay/paplay/ffplay)")
38
+ }
39
+
40
+ func stopPlatform() {
41
+ if audioCmd != nil && audioCmd.Process != nil {
42
+ _ = audioCmd.Process.Kill()
43
+ }
44
+ audioCmd = nil
45
+ cleanupTempWav()
46
+ }
@@ -0,0 +1,43 @@
1
+ //go:build windows
2
+
3
+ package engine
4
+
5
+ import (
6
+ "fmt"
7
+ "syscall"
8
+ "unsafe"
9
+ )
10
+
11
+ // Windows 音频后端: winmm.dll PlaySoundW (异步)。
12
+
13
+ var (
14
+ winmm = syscall.NewLazyDLL("winmm.dll")
15
+ playSoundW = winmm.NewProc("PlaySoundW")
16
+ )
17
+
18
+ const (
19
+ sndAsync = 0x0001
20
+ sndPurge = 0x0040
21
+ sndFilename = 0x00020000
22
+ )
23
+
24
+ func playPlatform(data []byte) error {
25
+ path, err := writeTempWav(data)
26
+ if err != nil {
27
+ return err
28
+ }
29
+ p, err := syscall.UTF16PtrFromString(path)
30
+ if err != nil {
31
+ return err
32
+ }
33
+ r, _, _ := playSoundW.Call(uintptr(unsafe.Pointer(p)), 0, sndFilename|sndAsync)
34
+ if r == 0 {
35
+ return fmt.Errorf("PlaySoundW failed")
36
+ }
37
+ return nil
38
+ }
39
+
40
+ func stopPlatform() {
41
+ playSoundW.Call(0, 0, sndPurge)
42
+ cleanupTempWav()
43
+ }
@@ -0,0 +1,271 @@
1
+ package engine
2
+
3
+ import (
4
+ "fmt"
5
+ "image"
6
+ "image/color"
7
+ "image/png"
8
+ "os"
9
+ "os/exec"
10
+ "path/filepath"
11
+ "runtime"
12
+ "strings"
13
+ "time"
14
+ )
15
+
16
+ // 2D 绘图画布 (Go 标准库 image/png 实现): 新建画布 → 着色 → 绘制形状/文本 → 导出 PNG。
17
+ // 宿主全局状态: 同一时刻一个「当前画布」与「当前颜色」。
18
+
19
+ var (
20
+ curCanvas *image.RGBA
21
+ curColor = color.RGBA{R: 0, G: 0, B: 0, A: 255} // 默认黑色画笔
22
+ )
23
+
24
+ func clampCoord(v uint64) int {
25
+ if v > 100000 {
26
+ return 100000
27
+ }
28
+ return int(v)
29
+ }
30
+
31
+ func (vm *vmState) canvasNew(w, h uint64) {
32
+ cw, ch := clampCoord(w), clampCoord(h)
33
+ if cw < 1 {
34
+ cw = 1
35
+ }
36
+ if ch < 1 {
37
+ ch = 1
38
+ }
39
+ curCanvas = image.NewRGBA(image.Rect(0, 0, cw, ch))
40
+ // 白色背景
41
+ for i := range curCanvas.Pix {
42
+ curCanvas.Pix[i] = 255
43
+ }
44
+ curColor = color.RGBA{R: 0, G: 0, B: 0, A: 255}
45
+ }
46
+
47
+ func (vm *vmState) canvasSetColor(rgb uint64) {
48
+ curColor = color.RGBA{
49
+ R: uint8((rgb >> 16) & 0xFF),
50
+ G: uint8((rgb >> 8) & 0xFF),
51
+ B: uint8(rgb & 0xFF),
52
+ A: 255,
53
+ }
54
+ }
55
+
56
+ func (vm *vmState) canvasRect(x, y, w, h uint64) {
57
+ if curCanvas == nil {
58
+ return
59
+ }
60
+ x0, y0 := clampCoord(x), clampCoord(y)
61
+ x1, y1 := x0+clampCoord(w), y0+clampCoord(h)
62
+ b := curCanvas.Bounds()
63
+ if x0 < b.Min.X {
64
+ x0 = b.Min.X
65
+ }
66
+ if y0 < b.Min.Y {
67
+ y0 = b.Min.Y
68
+ }
69
+ if x1 > b.Max.X {
70
+ x1 = b.Max.X
71
+ }
72
+ if y1 > b.Max.Y {
73
+ y1 = b.Max.Y
74
+ }
75
+ for py := y0; py < y1; py++ {
76
+ for px := x0; px < x1; px++ {
77
+ curCanvas.SetRGBA(px, py, curColor)
78
+ }
79
+ }
80
+ }
81
+
82
+ func (vm *vmState) canvasCircle(cx, cy, r uint64) {
83
+ if curCanvas == nil {
84
+ return
85
+ }
86
+ ccx, ccy := clampCoord(cx), clampCoord(cy)
87
+ rr := clampCoord(r)
88
+ rr2 := rr * rr
89
+ b := curCanvas.Bounds()
90
+ for py := ccy - rr; py <= ccy+rr; py++ {
91
+ for px := ccx - rr; px <= ccx+rr; px++ {
92
+ dx := px - ccx
93
+ dy := py - ccy
94
+ if dx*dx+dy*dy <= rr2 {
95
+ if px >= b.Min.X && px < b.Max.X && py >= b.Min.Y && py < b.Max.Y {
96
+ curCanvas.SetRGBA(px, py, curColor)
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ func (vm *vmState) canvasLine(x0, y0, x1, y1 uint64) {
104
+ if curCanvas == nil {
105
+ return
106
+ }
107
+ ax, ay := clampCoord(x0), clampCoord(y0)
108
+ bx, by := clampCoord(x1), clampCoord(y1)
109
+ b := curCanvas.Bounds()
110
+ // Bresenham
111
+ dx := bx - ax
112
+ if dx < 0 {
113
+ dx = -dx
114
+ }
115
+ sx := 1
116
+ if ax > bx {
117
+ sx = -1
118
+ }
119
+ dy := by - ay
120
+ if dy < 0 {
121
+ dy = -dy
122
+ }
123
+ sy := 1
124
+ if ay > by {
125
+ sy = -1
126
+ }
127
+ err := dx - dy
128
+ for {
129
+ if ax >= b.Min.X && ax < b.Max.X && ay >= b.Min.Y && ay < b.Max.Y {
130
+ curCanvas.SetRGBA(ax, ay, curColor)
131
+ }
132
+ if ax == bx && ay == by {
133
+ break
134
+ }
135
+ e2 := 2 * err
136
+ if e2 > -dy {
137
+ err -= dy
138
+ ax += sx
139
+ }
140
+ if e2 < dx {
141
+ err += dx
142
+ ay += sy
143
+ }
144
+ }
145
+ }
146
+
147
+ // font5x7: 内置 5x7 点阵字库 (ASCII 数字/大写/常用标点; 小写映射为大写)。
148
+ type glyph [7]string
149
+
150
+ var font5x7 = map[rune]glyph{
151
+ ' ': {" ", " ", " ", " ", " ", " ", " "},
152
+ '0': {" ### ", "# #", "# ##", "# # #", "## #", "# #", " ### "},
153
+ '1': {" # ", " ## ", " # ", " # ", " # ", " # ", " ### "},
154
+ '2': {" ### ", "# #", " #", " # ", " # ", " # ", "#####"},
155
+ '3': {"#####", " #", " # ", " ## ", " #", "# #", " ### "},
156
+ '4': {" # ", " ## ", " # # ", "# # ", "#####", " # ", " # "},
157
+ '5': {"#####", "# ", "#### ", " #", " #", "# #", " ### "},
158
+ '6': {" ## ", " # ", "# ", "#### ", "# #", "# #", " ### "},
159
+ '7': {"#####", " #", " # ", " # ", " # ", " # ", " # "},
160
+ '8': {" ### ", "# #", "# #", " ### ", "# #", "# #", " ### "},
161
+ '9': {" ### ", "# #", "# #", " ####", " #", " # ", " ## "},
162
+ 'A': {" ### ", "# #", "# #", "#####", "# #", "# #", "# #"},
163
+ 'B': {"#### ", "# #", "# #", "#### ", "# #", "# #", "#### "},
164
+ 'C': {" ### ", "# #", "# ", "# ", "# ", "# #", " ### "},
165
+ 'D': {"#### ", "# #", "# #", "# #", "# #", "# #", "#### "},
166
+ 'E': {"#####", "# ", "# ", "#### ", "# ", "# ", "#####"},
167
+ 'F': {"#####", "# ", "# ", "#### ", "# ", "# ", "# "},
168
+ 'G': {" ### ", "# #", "# ", "# ###", "# #", "# #", " ####"},
169
+ 'H': {"# #", "# #", "# #", "#####", "# #", "# #", "# #"},
170
+ 'I': {" ### ", " # ", " # ", " # ", " # ", " # ", " ### "},
171
+ 'J': {" #", " #", " #", " #", "# #", "# #", " ### "},
172
+ 'K': {"# #", "# # ", "# # ", "## ", "# # ", "# # ", "# #"},
173
+ 'L': {"# ", "# ", "# ", "# ", "# ", "# ", "#####"},
174
+ 'M': {"# #", "## ##", "# # #", "# # #", "# #", "# #", "# #"},
175
+ 'N': {"# #", "## #", "# # #", "# ##", "# #", "# #", "# #"},
176
+ 'O': {" ### ", "# #", "# #", "# #", "# #", "# #", " ### "},
177
+ 'P': {"#### ", "# #", "# #", "#### ", "# ", "# ", "# "},
178
+ 'Q': {" ### ", "# #", "# #", "# #", "# # #", "# # ", " ## #"},
179
+ 'R': {"#### ", "# #", "# #", "#### ", "# # ", "# # ", "# #"},
180
+ 'S': {" ####", "# ", "# ", " ### ", " #", " #", "#### "},
181
+ 'T': {"#####", " # ", " # ", " # ", " # ", " # ", " # "},
182
+ 'U': {"# #", "# #", "# #", "# #", "# #", "# #", " ### "},
183
+ 'V': {"# #", "# #", "# #", "# #", "# #", " # # ", " # "},
184
+ 'W': {"# #", "# #", "# #", "# # #", "# # #", "## ##", "# #"},
185
+ 'X': {"# #", "# #", " # # ", " # ", " # # ", "# #", "# #"},
186
+ 'Y': {"# #", "# #", " # # ", " # ", " # ", " # ", " # "},
187
+ 'Z': {"#####", " #", " # ", " # ", " # ", "# ", "#####"},
188
+ '.': {" ", " ", " ", " ", " ", " ## ", " ## "},
189
+ ',': {" ", " ", " ", " ", " ## ", " # ", " # "},
190
+ ':': {" ", " ## ", " ## ", " ", " ## ", " ## ", " "},
191
+ '-': {" ", " ", " ", "#####", " ", " ", " "},
192
+ '!': {" # ", " # ", " # ", " # ", " # ", " ", " # "},
193
+ '?': {" ### ", "# #", " #", " # ", " # ", " ", " # "},
194
+ '/': {" #", " #", " # ", " # ", " # ", "# ", "# "},
195
+ }
196
+
197
+ func (vm *vmState) canvasText(x, y uint64, s string) {
198
+ if curCanvas == nil {
199
+ return
200
+ }
201
+ ox, oy := clampCoord(x), clampCoord(y)
202
+ s = strings.ToUpper(s)
203
+ b := curCanvas.Bounds()
204
+ px := ox
205
+ for _, r := range s {
206
+ g, ok := font5x7[r]
207
+ if !ok {
208
+ g = font5x7['?']
209
+ }
210
+ for row := 0; row < 7; row++ {
211
+ for col := 0; col < 5; col++ {
212
+ if g[row][col] == '#' {
213
+ cx := px + col
214
+ cy := oy + row
215
+ if cx >= b.Min.X && cx < b.Max.X && cy >= b.Min.Y && cy < b.Max.Y {
216
+ curCanvas.SetRGBA(cx, cy, curColor)
217
+ }
218
+ }
219
+ }
220
+ }
221
+ px += 6
222
+ }
223
+ }
224
+
225
+ func (vm *vmState) canvasSave(path string) uint64 {
226
+ if curCanvas == nil {
227
+ return mask64 // -1
228
+ }
229
+ f, err := os.Create(path)
230
+ if err != nil {
231
+ return mask64
232
+ }
233
+ defer f.Close()
234
+ if err := png.Encode(f, curCanvas); err != nil {
235
+ return mask64
236
+ }
237
+ return 0
238
+ }
239
+
240
+ // canvasShow 保存当前画布到临时 PNG 并用系统查看器打开 (跨平台 "窗口")。
241
+ func (vm *vmState) canvasShow() uint64 {
242
+ if curCanvas == nil {
243
+ return mask64 // -1
244
+ }
245
+ path := filepath.Join(os.TempDir(),
246
+ fmt.Sprintf("codecin_canvas_%d.png", time.Now().UnixNano()))
247
+ f, err := os.Create(path)
248
+ if err != nil {
249
+ return mask64
250
+ }
251
+ if err := png.Encode(f, curCanvas); err != nil {
252
+ _ = f.Close()
253
+ return mask64
254
+ }
255
+ _ = f.Close()
256
+ openViewer(path)
257
+ return 0
258
+ }
259
+
260
+ func openViewer(path string) {
261
+ var cmd *exec.Cmd
262
+ switch runtime.GOOS {
263
+ case "windows":
264
+ cmd = exec.Command("cmd", "/c", "start", "", path)
265
+ case "darwin":
266
+ cmd = exec.Command("open", path)
267
+ default:
268
+ cmd = exec.Command("xdg-open", path)
269
+ }
270
+ _ = cmd.Start()
271
+ }
@@ -0,0 +1,86 @@
1
+ package engine
2
+
3
+ // CROM v3: magic 'CROM' + version u8 + mem_size u32 + flags u8 + crc32 u32
4
+ // + 2B reserved + payload (zlib 默认压缩, 与 Python zlib level 6 兼容)
5
+
6
+ import (
7
+ "bytes"
8
+ "compress/zlib"
9
+ "encoding/binary"
10
+ "hash/crc32"
11
+ "io"
12
+ )
13
+
14
+ const cromVersion = 3
15
+
16
+ // cromMaxTrailer 是载荷允许超出头部 mem_size 的最大余量 (MMU 页表尾部元数据)。
17
+ // 必须与 Python 侧 codecin/crom.py: CROM_MAX_TRAILER 保持一致。
18
+ const cromMaxTrailer = 4 << 20
19
+
20
+ // CromPack 打包内存镜像为 CROM 字节流 (compress=true 时 zlib 压缩)。
21
+ func CromPack(data []byte, compress bool) []byte {
22
+ var payload []byte
23
+ flags := byte(0)
24
+ if compress {
25
+ flags = 0x01
26
+ var buf bytes.Buffer
27
+ // zlib.NewWriter 使用默认压缩级别 (flate.DefaultCompression = 6)
28
+ w := zlib.NewWriter(&buf)
29
+ _, _ = w.Write(data)
30
+ _ = w.Close()
31
+ payload = buf.Bytes()
32
+ } else {
33
+ payload = data
34
+ }
35
+
36
+ out := make([]byte, 16+len(payload))
37
+ copy(out[0:4], "CROM")
38
+ out[4] = cromVersion
39
+ binary.LittleEndian.PutUint32(out[5:9], uint32(len(data)))
40
+ out[9] = flags
41
+ binary.LittleEndian.PutUint32(out[10:14], crc32.ChecksumIEEE(payload))
42
+ // [14:16] reserved 0
43
+ copy(out[16:], payload)
44
+ return out
45
+ }
46
+
47
+ // CromUnpack 解包 CROM, 返回 (raw, flags, ok)。
48
+ func CromUnpack(data []byte) ([]byte, int, bool) {
49
+ if len(data) < 16 || string(data[0:4]) != "CROM" {
50
+ return nil, 0, false
51
+ }
52
+ if data[4] != cromVersion {
53
+ return nil, 0, false
54
+ }
55
+ memSize := binary.LittleEndian.Uint32(data[5:9])
56
+ flags := data[9]
57
+ checksum := binary.LittleEndian.Uint32(data[10:14])
58
+ payload := data[16:]
59
+ if crc32.ChecksumIEEE(payload) != checksum {
60
+ return nil, 0, false
61
+ }
62
+
63
+ var raw []byte
64
+ if flags&0x01 != 0 {
65
+ r, err := zlib.NewReader(bytes.NewReader(payload))
66
+ if err != nil {
67
+ return nil, 0, false
68
+ }
69
+ // 解压上限取自文件头声明的 mem_size + MMU 尾部余量: 防止 zip bomb
70
+ // (旧实现 io.ReadAll 无上限, 65KB 的合法 CROM 可解出 64MB+)。
71
+ limit := int64(memSize) + cromMaxTrailer
72
+ limited := io.LimitReader(r, limit+1)
73
+ raw, err = io.ReadAll(limited)
74
+ _ = r.Close()
75
+ if err != nil {
76
+ return nil, 0, false
77
+ }
78
+ } else {
79
+ raw = payload
80
+ }
81
+ // mem_size 是物理内存长度; 之后允许跟一段 MMU 页表尾部元数据
82
+ if uint32(len(raw)) < memSize || len(raw) > int(memSize)+cromMaxTrailer {
83
+ return nil, 0, false
84
+ }
85
+ return raw, int(flags), true
86
+ }
@@ -0,0 +1,93 @@
1
+ package engine
2
+
3
+ import (
4
+ "encoding/binary"
5
+ "fmt"
6
+ "math"
7
+
8
+ "codecin-native/ir"
9
+ )
10
+
11
+ // UCBC 字节码编码 (与 Python codecin/native.py encode_program 严格一致)。
12
+ const (
13
+ bcMagic = "UCBC"
14
+ bcVersion = 1
15
+ )
16
+
17
+ // condCode: 条件助记符 -> 编号 (与 Python Cond.NAMES 一致)
18
+ var condCode = map[string]uint8{
19
+ "EQ": 0, "NE": 1, "CS": 2, "CC": 3, "MI": 4, "PL": 5, "VS": 6, "VC": 7,
20
+ "HI": 8, "LS": 9, "GE": 10, "LT": 11, "GT": 12, "LE": 13, "AL": 14, "NV": 15,
21
+ }
22
+
23
+ // EncodeProgram 把 IR 程序编码为 UCBC 字节码。
24
+ func EncodeProgram(prog ir.Program, entry int) ([]byte, error) {
25
+ labels := map[string]int{}
26
+ for k, v := range prog.Labels {
27
+ labels[k] = v
28
+ }
29
+ for k, v := range prog.DataLabels {
30
+ labels[k] = v
31
+ }
32
+
33
+ buf := make([]byte, 0, 13+len(prog.Instructions)*8)
34
+ buf = append(buf, bcMagic...)
35
+ buf = append(buf, bcVersion)
36
+ var tmp [8]byte
37
+ binary.LittleEndian.PutUint32(tmp[:4], uint32(entry&0xFFFFFFFF))
38
+ buf = append(buf, tmp[:4]...)
39
+ binary.LittleEndian.PutUint32(tmp[:4], uint32(len(prog.Instructions)&0xFFFFFFFF))
40
+ buf = append(buf, tmp[:4]...)
41
+
42
+ for _, ins := range prog.Instructions {
43
+ opv, ok := opcodeByName[ins.Op]
44
+ if !ok {
45
+ return nil, fmt.Errorf("unknown opcode: %s", ins.Op)
46
+ }
47
+ buf = append(buf, opv, byte(len(ins.Args)))
48
+ for _, a := range ins.Args {
49
+ kind, value, extra, err := encodeOperand(a, labels)
50
+ if err != nil {
51
+ return nil, err
52
+ }
53
+ buf = append(buf, kind)
54
+ binary.LittleEndian.PutUint64(tmp[:8], uint64(value))
55
+ buf = append(buf, tmp[:8]...)
56
+ binary.LittleEndian.PutUint64(tmp[:8], uint64(extra))
57
+ buf = append(buf, tmp[:8]...)
58
+ }
59
+ }
60
+ return buf, nil
61
+ }
62
+
63
+ func encodeOperand(op ir.Operand, labels map[string]int) (kind uint8, value, extra int64, err error) {
64
+ switch op.Kind {
65
+ case "reg":
66
+ return kindReg, op.V, 0, nil
67
+ case "imm":
68
+ return kindImm, op.V, 0, nil
69
+ case "vec":
70
+ return kindVec, op.V, 0, nil
71
+ case "veclane":
72
+ return kindVecLane, op.V, op.Off, nil
73
+ case "mem":
74
+ return kindMem, op.V, op.Off, nil
75
+ case "cond":
76
+ c, ok := condCode[op.Name]
77
+ if !ok {
78
+ return 0, 0, 0, fmt.Errorf("unknown cond: %s", op.Name)
79
+ }
80
+ return kindCond, int64(c), 0, nil
81
+ case "float":
82
+ return kindFloat, int64(math.Float64bits(op.F)), 0, nil
83
+ case "str":
84
+ return kindStr, op.V, 0, nil
85
+ case "label":
86
+ addr, ok := labels[op.Name]
87
+ if !ok {
88
+ return 0, 0, 0, fmt.Errorf("undefined label: %s", op.Name)
89
+ }
90
+ return kindImm, int64(addr), 0, nil
91
+ }
92
+ return 0, 0, 0, fmt.Errorf("unknown operand kind: %s", op.Kind)
93
+ }