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/jit.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""Python 级 JIT: 将无分支基本块动态编译为 Python 函数。
|
|
2
|
+
|
|
3
|
+
原生 Go 库不可用且显式 --jit 时使用。编译块仅包含直线指令
|
|
4
|
+
(数据传输/算术/逻辑/加载存储/栈), 遇到控制流指令即结束块。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
from .isa import Constants
|
|
10
|
+
|
|
11
|
+
MASK64 = 0xFFFFFFFFFFFFFFFF
|
|
12
|
+
|
|
13
|
+
# 可安全编译进直线块的指令
|
|
14
|
+
_JIT_OPS = {
|
|
15
|
+
'MOV', 'ADD', 'SUB', 'MUL', 'DIV', 'AND', 'OR', 'XOR', 'SHL', 'SHR',
|
|
16
|
+
'INC', 'DEC', 'ADDI', 'XORI', 'ORI', 'ANDI', 'LSL', 'LSR',
|
|
17
|
+
'LDR', 'STR', 'LOAD', 'STORE', 'LB', 'LH', 'LW', 'LD', 'SB', 'SH', 'SW', 'SD',
|
|
18
|
+
'PUSH', 'POP', 'NOP', 'MVN',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_BRANCH_OPS = Constants.BRANCH_OPS | {'HALT', 'SYS', 'IN', 'OUT'}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class JITCompiler:
|
|
25
|
+
def __init__(self, logger=None):
|
|
26
|
+
self.logger = logger
|
|
27
|
+
self.blocks: Dict[int, Callable] = {}
|
|
28
|
+
self.block_ranges: Dict[int, Tuple[int, int]] = {}
|
|
29
|
+
self.compilation_count = 0
|
|
30
|
+
self.total_calls = 0
|
|
31
|
+
self.cache_hits = 0
|
|
32
|
+
|
|
33
|
+
def _dbg(self, msg: str) -> None:
|
|
34
|
+
if self.logger is not None:
|
|
35
|
+
self.logger.debug(msg)
|
|
36
|
+
|
|
37
|
+
# ---------------- 代码生成 ----------------
|
|
38
|
+
|
|
39
|
+
def _reg_read(self, n: int) -> str:
|
|
40
|
+
if n == 31:
|
|
41
|
+
return '0'
|
|
42
|
+
if n == Constants.SP_REG:
|
|
43
|
+
return 'cpu.sp'
|
|
44
|
+
return f'R[{n}]'
|
|
45
|
+
|
|
46
|
+
def _reg_write(self, n: int, expr: str) -> str:
|
|
47
|
+
if n == 31:
|
|
48
|
+
return ''
|
|
49
|
+
if n == Constants.SP_REG:
|
|
50
|
+
return f'cpu.sp = ({expr}) & MASK'
|
|
51
|
+
return f'R[{n}] = ({expr}) & MASK'
|
|
52
|
+
|
|
53
|
+
def _val(self, op: Tuple) -> Optional[str]:
|
|
54
|
+
kind = op[0]
|
|
55
|
+
if kind == 'reg':
|
|
56
|
+
return self._reg_read(op[1])
|
|
57
|
+
if kind == 'imm':
|
|
58
|
+
return str(op[1] & MASK64)
|
|
59
|
+
if kind == 'mem':
|
|
60
|
+
return None # 内存作为值操作不编译
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def _mem_addr(self, op: Tuple) -> Optional[str]:
|
|
64
|
+
if op[0] != 'mem':
|
|
65
|
+
return None
|
|
66
|
+
base, off = op[1], op[2]
|
|
67
|
+
if base >= 0:
|
|
68
|
+
return f'({self._reg_read(base)} + {off})'
|
|
69
|
+
return str(off)
|
|
70
|
+
|
|
71
|
+
def _gen(self, opcode: str, args: List[Tuple]) -> Optional[List[str]]:
|
|
72
|
+
try:
|
|
73
|
+
if opcode == 'MOV':
|
|
74
|
+
val = self._val(args[1])
|
|
75
|
+
return [self._reg_write(args[0][1], val)] if val is not None else None
|
|
76
|
+
if opcode in ('ADD', 'SUB', 'MUL', 'AND', 'OR', 'XOR'):
|
|
77
|
+
sym = {'ADD': '+', 'SUB': '-', 'MUL': '*', 'AND': '&',
|
|
78
|
+
'OR': '|', 'XOR': '^'}[opcode]
|
|
79
|
+
val = self._val(args[1])
|
|
80
|
+
rd = args[0][1]
|
|
81
|
+
return [self._reg_write(rd, f'{self._reg_read(rd)} {sym} {val}')] if val else None
|
|
82
|
+
if opcode in ('SHL', 'SHR'):
|
|
83
|
+
sym = '<<' if opcode == 'SHL' else '>>'
|
|
84
|
+
val = self._val(args[1])
|
|
85
|
+
rd = args[0][1]
|
|
86
|
+
if val is None:
|
|
87
|
+
return None
|
|
88
|
+
if opcode == 'SHR':
|
|
89
|
+
return [f'_v = ({self._reg_read(rd)} & MASK) >> (({val}) & 63)',
|
|
90
|
+
self._reg_write(rd, '_v')]
|
|
91
|
+
return [self._reg_write(rd, f'{self._reg_read(rd)} {sym} (({val}) & 63)')]
|
|
92
|
+
if opcode == 'DIV':
|
|
93
|
+
rd = args[0][1]
|
|
94
|
+
val = self._val(args[1])
|
|
95
|
+
if val is None:
|
|
96
|
+
return None
|
|
97
|
+
# 有符号除法, 向零截断 (与解释器一致)
|
|
98
|
+
return [f'_d = {val}',
|
|
99
|
+
'if _d == 0: raise ExecutionError("Division by zero")',
|
|
100
|
+
'_n = ' + self._reg_read(rd),
|
|
101
|
+
'_sn = _n if _n < (1 << 63) else _n - (1 << 64)',
|
|
102
|
+
'_sd = _d if _d < (1 << 63) else _d - (1 << 64)',
|
|
103
|
+
'_q = abs(_sn) // abs(_sd)',
|
|
104
|
+
'_q = _q if (_sn < 0) == (_sd < 0) else -_q',
|
|
105
|
+
self._reg_write(rd, '_q')]
|
|
106
|
+
if opcode == 'INC':
|
|
107
|
+
rd = args[0][1]
|
|
108
|
+
return [self._reg_write(rd, f'{self._reg_read(rd)} + 1')]
|
|
109
|
+
if opcode == 'DEC':
|
|
110
|
+
rd = args[0][1]
|
|
111
|
+
return [self._reg_write(rd, f'{self._reg_read(rd)} - 1')]
|
|
112
|
+
if opcode == 'MVN':
|
|
113
|
+
val = self._val(args[1])
|
|
114
|
+
return [self._reg_write(args[0][1], f'~({val})')] if val else None
|
|
115
|
+
if opcode in ('ADDI',):
|
|
116
|
+
rd, rs = args[0][1], args[1][1]
|
|
117
|
+
val = self._val(args[2])
|
|
118
|
+
return [self._reg_write(rd, f'{self._reg_read(rs)} + {val}')] if val else None
|
|
119
|
+
if opcode in ('XORI', 'ORI', 'ANDI'):
|
|
120
|
+
sym = {'XORI': '^', 'ORI': '|', 'ANDI': '&'}[opcode]
|
|
121
|
+
rd, rs = args[0][1], args[1][1]
|
|
122
|
+
val = self._val(args[2])
|
|
123
|
+
return [self._reg_write(rd, f'{self._reg_read(rs)} {sym} {val}')] if val else None
|
|
124
|
+
if opcode in ('LSL', 'LSR'):
|
|
125
|
+
rd, rn = args[0][1], args[1][1]
|
|
126
|
+
val = self._val(args[2])
|
|
127
|
+
if val is None:
|
|
128
|
+
return None
|
|
129
|
+
if opcode == 'LSL':
|
|
130
|
+
return [self._reg_write(rd, f'{self._reg_read(rn)} << (({val}) & 63)')]
|
|
131
|
+
return [f'_v = ({self._reg_read(rn)} & MASK) >> (({val}) & 63)',
|
|
132
|
+
self._reg_write(rd, '_v')]
|
|
133
|
+
if opcode in ('LDR', 'LOAD', 'LW'):
|
|
134
|
+
addr = self._mem_addr(args[1]) if args[1][0] == 'mem' else self._val(args[1])
|
|
135
|
+
rd = args[0][1]
|
|
136
|
+
if addr is None:
|
|
137
|
+
return None
|
|
138
|
+
return [self._reg_write(rd, f'mem.read_dword({addr})')]
|
|
139
|
+
if opcode in ('STR', 'STORE', 'SW'):
|
|
140
|
+
addr = self._mem_addr(args[1]) if args[1][0] == 'mem' else self._val(args[1])
|
|
141
|
+
rs = args[0][1]
|
|
142
|
+
if addr is None:
|
|
143
|
+
return None
|
|
144
|
+
return [f'mem.write_dword({addr}, {self._reg_read(rs)} & 0xFFFFFFFF)']
|
|
145
|
+
if opcode == 'LD':
|
|
146
|
+
addr = self._mem_addr(args[1]) if args[1][0] == 'mem' else self._val(args[1])
|
|
147
|
+
return [self._reg_write(args[0][1], f'mem.read_qword({addr})')] if addr else None
|
|
148
|
+
if opcode == 'SD':
|
|
149
|
+
addr = self._mem_addr(args[1]) if args[1][0] == 'mem' else self._val(args[1])
|
|
150
|
+
return [f'mem.write_qword({addr}, {self._reg_read(args[0][1])})'] if addr else None
|
|
151
|
+
if opcode in ('LB', 'LH'):
|
|
152
|
+
width = 1 if opcode == 'LB' else 2
|
|
153
|
+
fn = 'read_byte' if width == 1 else 'read_word'
|
|
154
|
+
addr = self._mem_addr(args[1]) if args[1][0] == 'mem' else self._val(args[1])
|
|
155
|
+
if addr is None:
|
|
156
|
+
return None
|
|
157
|
+
bits = width * 8
|
|
158
|
+
return [f'_v = mem.{fn}({addr})',
|
|
159
|
+
f'if _v & {1 << (bits - 1)}: _v -= {1 << bits}',
|
|
160
|
+
self._reg_write(args[0][1], '_v')]
|
|
161
|
+
if opcode in ('SB', 'SH'):
|
|
162
|
+
mask = '0xFF' if opcode == 'SB' else '0xFFFF'
|
|
163
|
+
fn = 'write_byte' if opcode == 'SB' else 'write_word'
|
|
164
|
+
addr = self._mem_addr(args[1]) if args[1][0] == 'mem' else self._val(args[1])
|
|
165
|
+
if addr is None:
|
|
166
|
+
return None
|
|
167
|
+
return [f'mem.{fn}({addr}, {self._reg_read(args[0][1])} & {mask})']
|
|
168
|
+
if opcode == 'PUSH':
|
|
169
|
+
val = self._val(args[0])
|
|
170
|
+
if val is None:
|
|
171
|
+
return None
|
|
172
|
+
return ['cpu.sp = (cpu.sp - 8) & MASK',
|
|
173
|
+
f'mem.write_qword(cpu.sp, ({val}) & MASK)']
|
|
174
|
+
if opcode == 'POP':
|
|
175
|
+
rd = args[0][1]
|
|
176
|
+
return ['_v = mem.read_qword(cpu.sp)',
|
|
177
|
+
'cpu.sp = (cpu.sp + 8) & MASK',
|
|
178
|
+
self._reg_write(rd, '_v')]
|
|
179
|
+
if opcode == 'NOP':
|
|
180
|
+
return ['pass']
|
|
181
|
+
except Exception:
|
|
182
|
+
return None
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def compile_block(self, cpu) -> Optional[int]:
|
|
186
|
+
"""编译 cpu.pc 处开始的基本块, 返回块结束 PC。"""
|
|
187
|
+
start = cpu.pc
|
|
188
|
+
if start in self.blocks:
|
|
189
|
+
self.cache_hits += 1
|
|
190
|
+
if self.logger is not None and self.logger.is_debug:
|
|
191
|
+
self._dbg(f"JIT cache hit: block @0x{start:x} "
|
|
192
|
+
f"(hits={self.cache_hits})")
|
|
193
|
+
return self.block_ranges[start][1]
|
|
194
|
+
|
|
195
|
+
end = start
|
|
196
|
+
body: List[str] = []
|
|
197
|
+
for i in range(start, min(start + 32, len(cpu.instructions))):
|
|
198
|
+
opcode, args = cpu.instructions[i]
|
|
199
|
+
if opcode in _BRANCH_OPS or opcode not in _JIT_OPS:
|
|
200
|
+
break
|
|
201
|
+
lines = self._gen(opcode, args)
|
|
202
|
+
if lines is None:
|
|
203
|
+
break
|
|
204
|
+
body.extend(f' {ln}' for ln in lines if ln)
|
|
205
|
+
end = i + 1
|
|
206
|
+
|
|
207
|
+
if end <= start:
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
source = (
|
|
211
|
+
"def block(cpu, mem, R):\n"
|
|
212
|
+
" MASK = 0xFFFFFFFFFFFFFFFF\n"
|
|
213
|
+
+ ("\n".join(body) if body else " pass") + "\n"
|
|
214
|
+
" return None\n"
|
|
215
|
+
)
|
|
216
|
+
try:
|
|
217
|
+
from .errors import ExecutionError
|
|
218
|
+
namespace: Dict[str, Any] = {'ExecutionError': ExecutionError}
|
|
219
|
+
exec(compile(source, '<jit-block>', 'exec'), namespace)
|
|
220
|
+
func = namespace['block']
|
|
221
|
+
except Exception as e:
|
|
222
|
+
cpu.logger.debug(f"JIT block compile failed at {start}: {e}")
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
self.blocks[start] = func
|
|
226
|
+
self.block_ranges[start] = (start, end)
|
|
227
|
+
self.compilation_count += 1
|
|
228
|
+
self._dbg(f"JIT compiled block 0x{start:x}-0x{end:x} "
|
|
229
|
+
f"({end - start} instrs, total {self.compilation_count})")
|
|
230
|
+
if self.logger is not None and self.logger.is_debug:
|
|
231
|
+
self._dbg('JIT block source:\n' + source.rstrip())
|
|
232
|
+
return end
|
|
233
|
+
|
|
234
|
+
def try_step(self, cpu) -> Optional[bool]:
|
|
235
|
+
"""尝试以 JIT 块执行; 不适合 JIT 时返回 None。"""
|
|
236
|
+
self.total_calls += 1
|
|
237
|
+
start = cpu.pc
|
|
238
|
+
if start < 0 or start >= len(cpu.instructions):
|
|
239
|
+
return False
|
|
240
|
+
opcode, _ = cpu.instructions[start]
|
|
241
|
+
if opcode in _BRANCH_OPS or opcode not in _JIT_OPS:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
end = self.compile_block(cpu)
|
|
245
|
+
if end is None:
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
func = self.blocks.get(start)
|
|
249
|
+
if func is None:
|
|
250
|
+
return None
|
|
251
|
+
try:
|
|
252
|
+
func(cpu, cpu.memory, cpu.regs._regs)
|
|
253
|
+
except Exception as e:
|
|
254
|
+
cpu.logger.debug(f"JIT execution failed: {e}")
|
|
255
|
+
self.blocks.pop(start, None)
|
|
256
|
+
return None
|
|
257
|
+
|
|
258
|
+
for i in range(start, end):
|
|
259
|
+
cpu.stats.record_instruction(cpu.instructions[i][0])
|
|
260
|
+
cpu.pc = end
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
264
|
+
return {
|
|
265
|
+
'blocks_compiled': self.compilation_count,
|
|
266
|
+
'cached_blocks': len(self.blocks),
|
|
267
|
+
'total_calls': self.total_calls,
|
|
268
|
+
'cache_hits': self.cache_hits,
|
|
269
|
+
'hit_rate': self.cache_hits / self.total_calls if self.total_calls else 0.0,
|
|
270
|
+
}
|
|
Binary file
|
codecin/logger.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from rich.logging import RichHandler
|
|
7
|
+
from rich.table import Table as RichTable
|
|
8
|
+
|
|
9
|
+
from .console import Console
|
|
10
|
+
|
|
11
|
+
_FORMAT = '%(message)s'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _render_plain(renderable) -> str:
|
|
15
|
+
"""将 rich 渲染对象渲染为纯文本 (用于日志消息)。"""
|
|
16
|
+
buf = io.StringIO()
|
|
17
|
+
from rich.console import Console as RC
|
|
18
|
+
rc = RC(file=buf, highlight=False, emoji=False, markup=False, width=120)
|
|
19
|
+
rc.print(renderable)
|
|
20
|
+
return buf.getvalue().rstrip('\n')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Logger:
|
|
24
|
+
|
|
25
|
+
LEVELS = {'DEBUG': 0, 'INFO': 1, 'WARNING': 2, 'ERROR': 3, 'CRITICAL': 4}
|
|
26
|
+
|
|
27
|
+
def __init__(self, console: Optional[Console] = None, level: str = 'INFO'):
|
|
28
|
+
self.console = console or Console()
|
|
29
|
+
self.level = self._parse_level(level)
|
|
30
|
+
|
|
31
|
+
self._log = logging.getLogger('codecin')
|
|
32
|
+
self._log.propagate = False
|
|
33
|
+
self._log.setLevel(logging.DEBUG) # handler 负责过滤
|
|
34
|
+
# 本类独占 'codecin' logger, 避免重复 handler
|
|
35
|
+
for h in list(self._log.handlers):
|
|
36
|
+
self._log.removeHandler(h)
|
|
37
|
+
|
|
38
|
+
self._handler = RichHandler(
|
|
39
|
+
console=self.console.rich,
|
|
40
|
+
show_time=True,
|
|
41
|
+
show_level=True,
|
|
42
|
+
show_path=False,
|
|
43
|
+
rich_tracebacks=True,
|
|
44
|
+
tracebacks_show_locals=False,
|
|
45
|
+
markup=False,
|
|
46
|
+
log_time_format='%H:%M:%S',
|
|
47
|
+
)
|
|
48
|
+
self._handler.setFormatter(logging.Formatter(_FORMAT))
|
|
49
|
+
self._handler.setLevel(self._to_logging_level(self.level))
|
|
50
|
+
self._log.addHandler(self._handler)
|
|
51
|
+
|
|
52
|
+
self._file_handler: Optional[logging.FileHandler] = None
|
|
53
|
+
self.log_file: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
# ---------------- 级别控制 ----------------
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _to_logging_level(codecin_level: int) -> int:
|
|
59
|
+
# codecin: DEBUG=0..CRITICAL=4 -> logging: 10..50
|
|
60
|
+
return (codecin_level + 1) * 10
|
|
61
|
+
|
|
62
|
+
def _parse_level(self, level: str) -> int:
|
|
63
|
+
return self.LEVELS.get(str(level).upper(), 1)
|
|
64
|
+
|
|
65
|
+
def set_level(self, level: str) -> None:
|
|
66
|
+
self.level = self._parse_level(level)
|
|
67
|
+
self._handler.setLevel(self._to_logging_level(self.level))
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def is_debug(self) -> bool:
|
|
71
|
+
return self.level <= self.LEVELS['DEBUG']
|
|
72
|
+
|
|
73
|
+
def set_log_file(self, filename: str) -> None:
|
|
74
|
+
self.log_file = filename
|
|
75
|
+
if self._file_handler is not None:
|
|
76
|
+
self._log.removeHandler(self._file_handler)
|
|
77
|
+
self._file_handler.close()
|
|
78
|
+
os.makedirs(os.path.dirname(os.path.abspath(filename)) or '.',
|
|
79
|
+
exist_ok=True)
|
|
80
|
+
fh = logging.FileHandler(filename, mode='w', encoding='utf-8')
|
|
81
|
+
fh.setFormatter(logging.Formatter(
|
|
82
|
+
'[%(asctime)s] [%(levelname)s] %(message)s', datefmt='%H:%M:%S'))
|
|
83
|
+
fh.setLevel(logging.DEBUG) # 文件记录全量
|
|
84
|
+
self._log.addHandler(fh)
|
|
85
|
+
self._file_handler = fh
|
|
86
|
+
|
|
87
|
+
# ---------------- 标准级别 ----------------
|
|
88
|
+
|
|
89
|
+
def debug(self, msg: str) -> None:
|
|
90
|
+
self._log.debug(msg)
|
|
91
|
+
|
|
92
|
+
def info(self, msg: str) -> None:
|
|
93
|
+
self._log.info(msg)
|
|
94
|
+
|
|
95
|
+
def warning(self, msg: str) -> None:
|
|
96
|
+
self._log.warning(msg)
|
|
97
|
+
|
|
98
|
+
def error(self, msg: str, exc_info: Any = None) -> None:
|
|
99
|
+
self._log.error(msg, exc_info=exc_info)
|
|
100
|
+
|
|
101
|
+
def critical(self, msg: str, exc_info: Any = None) -> None:
|
|
102
|
+
self._log.critical(msg, exc_info=exc_info)
|
|
103
|
+
|
|
104
|
+
def exception(self, msg: str) -> None:
|
|
105
|
+
"""rich 彩色堆栈回溯输出。"""
|
|
106
|
+
self._log.error(msg, exc_info=True)
|
|
107
|
+
|
|
108
|
+
# ---------------- 超详细调试辅助 ----------------
|
|
109
|
+
|
|
110
|
+
def trace(self, msg: str) -> None:
|
|
111
|
+
"""逐指令级追踪 (仅 DEBUG)。"""
|
|
112
|
+
self._log.debug(msg)
|
|
113
|
+
|
|
114
|
+
def dump(self, title: str, fields: Dict[str, Any]) -> None:
|
|
115
|
+
"""以表格输出调试快照 (仅 DEBUG)。"""
|
|
116
|
+
if not self.is_debug:
|
|
117
|
+
return
|
|
118
|
+
t = RichTable(title=title, show_header=False, border_style='dim',
|
|
119
|
+
padding=(0, 1), highlight=False)
|
|
120
|
+
t.add_column(style='bold cyan', no_wrap=True)
|
|
121
|
+
t.add_column()
|
|
122
|
+
for k, v in fields.items():
|
|
123
|
+
t.add_row(str(k), str(v))
|
|
124
|
+
self._log.debug(_render_plain(t))
|
|
125
|
+
|
|
126
|
+
def hexdump(self, title: str, addr: int, data: bytes,
|
|
127
|
+
width: int = 16) -> None:
|
|
128
|
+
"""内存十六进制转储 (仅 DEBUG)。"""
|
|
129
|
+
if not self.is_debug:
|
|
130
|
+
return
|
|
131
|
+
lines = []
|
|
132
|
+
for off in range(0, min(len(data), 256), width):
|
|
133
|
+
chunk = data[off:off + width]
|
|
134
|
+
hexs = ' '.join(f'{b:02x}' for b in chunk)
|
|
135
|
+
asc = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
|
|
136
|
+
lines.append(f' {addr + off:06x} {hexs:<{width * 3}} {asc}')
|
|
137
|
+
if len(data) > 256:
|
|
138
|
+
lines.append(f' ... ({len(data)} bytes total)')
|
|
139
|
+
self._log.debug(f'{title}\n' + '\n'.join(lines))
|
|
140
|
+
|
|
141
|
+
def close(self) -> None:
|
|
142
|
+
if self._file_handler is not None:
|
|
143
|
+
self._log.removeHandler(self._file_handler)
|
|
144
|
+
self._file_handler.close()
|
|
145
|
+
self._file_handler = None
|