From 825919b2021984e8abdd186132d2ae95da49a7c1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 7 Oct 2026 08:48:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(compiler):=20=E4=BF=AE=E5=A4=8D=20ViperApps?= =?UTF-8?q?=20=E6=9A=B4=E9=9C=B2=E7=9A=84=203=20=E5=A4=84=E7=BC=BA?= =?UTF-8?q?=E9=99=B7=EF=BC=88vqt6=20=E8=BF=94=E5=9B=9E=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=20/=20MLIR=20=E5=B8=B8=E9=87=8F=E5=91=BD=E5=90=8D=20/=20scratc?= =?UTF-8?q?h=20=E5=A4=8D=E7=94=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) vqt6/__init__.vp:6 处类实例返回注解由引号前向引用(QAction/QMenu/QTreeWidgetItem)改为 T*——引号前向引用解析为结构体值类型,而实例是指针,致 QAction Foo*→Foo CastError(4 个 Qt 系工程失败)。 2) mlirlite/__function.vp:常量自动 SSA 名 %cN 避让用户同名标识符(形参 c1/c2 与常量 %c1 冲突 => mlir-opt redefinition of SSA value,致 ViperMNIST 失败)。 3) mlirlite/__function.vp + __module.vp:新增 MlirTablesReset,使打印窗口内的表/scratch 跨函数复用(原每函数分配约 153KiB 且窗口内不回收 => 1GiB 主池被榨干,致 ViperFLProject parse scratch allocation failed)。 --- mlirlite/__function.vp | 201 +++++++++++++++++++++++++++++++++++++++-- mlirlite/__module.vp | 7 +- vqt6/__init__.vp | 12 +-- 3 files changed, 206 insertions(+), 14 deletions(-) diff --git a/mlirlite/__function.vp b/mlirlite/__function.vp index ed14277..9ce386e 100644 --- a/mlirlite/__function.vp +++ b/mlirlite/__function.vp @@ -1158,6 +1158,14 @@ _MLIR_ConstVal: t.CPtr = None _MLIR_ConstName: t.CPtr = None _MLIR_ConstCount: int = 0 +# Next display number for auto constant SSA names (%c): decoupled from the table index +# _MLIR_ConstCount so the number can skip user SSA names literally named c0/c1/... (see +# _MlirConstGet / _MlirCollectUsedCNames), while identical constants still share one name. +_MLIR_ConstNameSeq: int = 0 +# Used-name table (i32[MLIR_CONST_CAP]): slot d = 1 iff the current function already uses an SSA +# name c (param or local), so the constant namer must skip d. +_MLIR_UsedC: t.CPtr = None + # bitcast no-op alias table + cmpxchg result binding table: result name -> source operand / extractvalue result name # (cmpxchg's extractvalue result name is deterministically derived from the result name, not a counter; see _MlirCxResName) _MLIR_AliasFrom: t.CPtr = None @@ -1169,8 +1177,10 @@ _MLIR_AliasCount: int = 0 # stack, so the line text is stashed in __types.vp (via MlirSetContextLine) and printed with the error. _MLIR_CurLine: str = None -# Parse-time scratch buffers (allocated once per FunctionPrint, reused throughout; -# text that must outlive a line is dup'd on table/alias insertion, see _mlir_dup) +# Parse-time scratch buffers. Allocated once per print window and reused across every FunctionPrint +# in it (the tables/scratch are ~150 KiB with buddy rounding, so allocating them per function filled +# the 1 GB main pool for function-heavy modules; see _MlirTablesInit / MlirTablesReset). Text that +# must outlive a line is dup'd on table/alias insertion, see _mlir_dup). _SC_Op: str = None _SC_Tb: str = None _SC_Ty: str = None @@ -1192,6 +1202,26 @@ _SC_B1: str = None _SC_B2: str = None _SC_Buf: str = None +# Reuse guard: 1 once the tables/scratch above are live for _MlirTablesPool. _MlirTablesInit +# allocates only when the guard is clear; every FunctionPrint in the same print window then reuses +# the buffers (logical state is reset per function). MlirTablesReset clears the guard at PrintModule +# entry so a window whose pool was reset_to'd reallocates from the current pool (a cached pointer +# into a reclaimed window would dangle). +_MlirTablesValid: int = 0 +_MlirTablesPool: memhub.MemBuddy* = None + + +def MlirTablesReset(): + """Invalidate the parse-time tables/scratch so the next FunctionPrint reallocates from the pool. + + Mirrors the global-region scratch pattern (MlirGvResetScratch): a print window may run after the + pool was reset_to'd, so cached pointers must not be reused across windows. Called at PrintModule + entry; FunctionPrint called standalone keeps its buffers (its caller owns the pool lifetime). + """ + global _MlirTablesValid, _SC_Op + _MlirTablesValid = 0 + _SC_Op = None + def _mlir_slotp(buf: t.CPtr, idx: int) -> str*: """Table slot address (buf + idx*8)""" @@ -1212,25 +1242,46 @@ def _mlir_dup(s: str) -> str: def _MlirTablesInit(pool: memhub.MemBuddy*): - """Initialize the two tables and the parse scratch buffers""" - global _MLIR_Pool, _MLIR_Pass, _MLIR_ConstTy, _MLIR_ConstVal, _MLIR_ConstName, _MLIR_ConstCount, _MLIR_AliasFrom, _MLIR_AliasTo, _MLIR_AliasCount, _SC_Op, _SC_Tb, _SC_Ty, _SC_Ty2, _SC_V1, _SC_V2, _SC_V3, _SC_Callee, _SC_Args, _SC_ArgTs, _SC_IdxV, _SC_IdxT, _SC_Mt, _SC_Tmp, _SC_Tmp2, _SC_P1, _SC_P2, _SC_B1, _SC_B2, _SC_Buf + """Initialize (or reuse) the constant/alias tables and the parse scratch buffers. + + The buffers live for the whole print window: the first FunctionPrint allocates them and every + later one reuses them, resetting only the logical state (counts + used-name table). Re-allocating + the ~150 KiB set per function (once per function, per dump mode) exhausted the 1 GB main pool on + function-heavy modules; the equivalent global-region scratch (MlirGvEnsureScratch) already reuses + this way. Callers that may reset the pool between prints must call MlirTablesReset first. + """ + global _MLIR_Pool, _MLIR_Pass, _MLIR_ConstTy, _MLIR_ConstVal, _MLIR_ConstName, _MLIR_ConstCount, _MLIR_ConstNameSeq, _MLIR_UsedC, _MLIR_AliasFrom, _MLIR_AliasTo, _MLIR_AliasCount, _SC_Op, _SC_Tb, _SC_Ty, _SC_Ty2, _SC_V1, _SC_V2, _SC_V3, _SC_Callee, _SC_Args, _SC_ArgTs, _SC_IdxV, _SC_IdxT, _SC_Mt, _SC_Tmp, _SC_Tmp2, _SC_P1, _SC_P2, _SC_B1, _SC_B2, _SC_Buf, _MlirTablesValid, _MlirTablesPool if pool is None: MlirFail("MLIR render: memory pool is null") + if _MlirTablesValid != 0 and _MlirTablesPool == pool: + # Reuse the live buffers: reset the per-function logical state. Entries past the reset counts + # are unreachable (all lookups scan [0, count)), so no table body needs clearing; only the + # used-name bitmap must be zeroed because _MlirCollectUsedCNames only ever sets bits. + _MLIR_Pool = pool + _MLIR_Pass = 0 + _MLIR_ConstCount = 0 + _MLIR_ConstNameSeq = 0 + _MLIR_AliasCount = 0 + string.memset(_MLIR_UsedC, 0, MLIR_CONST_CAP * 4) + return _MLIR_Pool = pool _MLIR_Pass = 0 _MLIR_ConstCount = 0 + _MLIR_ConstNameSeq = 0 _MLIR_AliasCount = 0 _MLIR_ConstTy = pool.alloc(MLIR_CONST_CAP * 8) _MLIR_ConstVal = pool.alloc(MLIR_CONST_CAP * 8) _MLIR_ConstName = pool.alloc(MLIR_CONST_CAP * 8) + _MLIR_UsedC = pool.alloc(MLIR_CONST_CAP * 4) _MLIR_AliasFrom = pool.alloc(MLIR_ALIAS_CAP * 8) _MLIR_AliasTo = pool.alloc(MLIR_ALIAS_CAP * 8) if _MLIR_ConstTy is None or _MLIR_ConstVal is None or _MLIR_ConstName is None \ - or _MLIR_AliasFrom is None or _MLIR_AliasTo is None: + or _MLIR_UsedC is None or _MLIR_AliasFrom is None or _MLIR_AliasTo is None: MlirFail("MLIR render: constant/alias table allocation failed") string.memset(_MLIR_ConstTy, 0, MLIR_CONST_CAP * 8) string.memset(_MLIR_ConstVal, 0, MLIR_CONST_CAP * 8) string.memset(_MLIR_ConstName, 0, MLIR_CONST_CAP * 8) + string.memset(_MLIR_UsedC, 0, MLIR_CONST_CAP * 4) string.memset(_MLIR_AliasFrom, 0, MLIR_ALIAS_CAP * 8) string.memset(_MLIR_AliasTo, 0, MLIR_ALIAS_CAP * 8) _SC_Op = pool.alloc(64) @@ -1259,6 +1310,8 @@ def _MlirTablesInit(pool: memhub.MemBuddy*): _SC_Ty[0] = '\0' _SC_Mt[0] = '\0' _SC_Tmp[0] = '\0' + _MlirTablesPool = pool + _MlirTablesValid = 1 # ============================================================ @@ -1303,9 +1356,131 @@ def _MlirResolve(name: str) -> str: return cur +def _MlirIsSsaNameChar(ch: char) -> int: + """Return 1 if ch may appear in a bare SSA/symbol name [A-Za-z0-9_$.] (mirrors _ll_name_needs_quote).""" + if ch >= 'a' and ch <= 'z': return 1 + if ch >= 'A' and ch <= 'Z': return 1 + if ch >= '0' and ch <= '9': return 1 + if ch == '_' or ch == '$' or ch == '.': return 1 + return 0 + + +def _usedc_set(idx: int, val: int): + """Write the used-name table slot (_MLIR_UsedC is an i32[MLIR_CONST_CAP] via explicit byte offset).""" + if _MLIR_UsedC is None: + return + elem_ptr: int* = (int*)(uint64(_MLIR_UsedC) + idx * 4) + c.DerefAs(elem_ptr, val) + + +def _usedc_get(idx: int) -> int: + """Read the used-name table slot (explicit byte offset, same convention as _map_get_i32).""" + if _MLIR_UsedC is None: + return 0 + elem_ptr: int* = (int*)(uint64(_MLIR_UsedC) + idx * 4) + return *elem_ptr + + +def _MlirMarkUsedCName(name: str): + """Mark name (an SSA name, with or without a leading '%') as used iff it is exactly c. + + Only numbers in [0, MLIR_CONST_CAP) can ever be produced by the constant namer (it fails before + reaching MLIR_CONST_CAP), so larger suffixes cannot collide and are ignored. A longer name like + `c1x` is not c and is ignored too (its token would not match `%c1`). + """ + if name is None: + return + p: str = name + if p[0] == '%': + p = p + 1 + if p[0] != 'c': + return + q: str = p + 1 + d: int = 0 + nd: int = 0 + while _is_digit_ch(q[0]) != 0: + if d < MLIR_CONST_CAP: + d = d * 10 + (int(q[0]) - 48) + nd += 1 + q = q + 1 + if nd == 0: + return + if _MlirIsSsaNameChar(q[0]) != 0: + return + if d < MLIR_CONST_CAP: + _usedc_set(d, 1) + + +def _MlirScanUsedCText(text: str): + """Mark every user SSA name of the form %c occurring in one intermediate line.""" + if text is None: + return + p: str = text + while p[0] != '\0': + if p[0] == '%': + cs: str = p + 1 + if cs[0] == 'c': + e: str = p + 2 + d: int = 0 + nd: int = 0 + while _is_digit_ch(e[0]) != 0: + if d < MLIR_CONST_CAP: + d = d * 10 + (int(e[0]) - 48) + nd += 1 + e = e + 1 + if nd > 0 and _MlirIsSsaNameChar(e[0]) == 0: + if d < MLIR_CONST_CAP: + _usedc_set(d, 1) + p = e + continue + p = p + 1 + + +def _MlirCollectUsedCNames(func: Function*): + """Mark all user SSA names of the form c in func (params + every intermediate line). + + The constant materializer auto-names constants %c; a param or local literally named c1/c2 + (legal Viper identifiers) would otherwise collide with it and mlir-opt reports + "redefinition of SSA value '%c1'". This pass records the exact used names so _MlirConstGet skips + those numbers; user names are never rewritten and there is no silent fallback (fail-fast below). + """ + if func is None: + return + params: GSList[Param]* = func.Params + if params is not None: + cur: Param* = params.Head + steps: int = 0 + while cur is not None: + steps += 1 + if steps > IR_DUMP_CHAIN_LIMIT: + MlirFail("MLIR render: parameter chain may be cyclic") + _MlirMarkUsedCName(cur.Name) + cur = cur.Next + blocks: GSList[BasicBlock]* = func.Blocks + if blocks is None: + return + blk: BasicBlock* = blocks.Head + blk_steps: int = 0 + while blk is not None: + blk_steps += 1 + if blk_steps > IR_DUMP_CHAIN_LIMIT: + MlirFail("MLIR render: function block chain may be cyclic") + if blk.Lines is not None: + lines: GSList[Line]* = blk.Lines + ln: Line* = lines.Head + ln_steps: int = 0 + while ln is not None: + ln_steps += 1 + if ln_steps > IR_DUMP_CHAIN_LIMIT: + MlirFail("MLIR render: block instruction chain may be cyclic") + _MlirScanUsedCText(ln.Buf) + ln = ln.Next + blk = blk.Next + + def _MlirConstGet(tytext: str, valtext: str) -> str: """Get (or create) the constant's materialized name %cN""" - global _MLIR_ConstCount + global _MLIR_ConstCount, _MLIR_ConstNameSeq for i in range(_MLIR_ConstCount): sp: str* = _mlir_slotp(_MLIR_ConstTy, i) if sp[0] is not None and string.strcmp(sp[0], tytext) == 0: @@ -1321,7 +1496,16 @@ def _MlirConstGet(tytext: str, valtext: str) -> str: nm: str = _MLIR_Pool.alloc(32) if nm is None: MlirFail("MLIR render: constant name allocation failed") - viperlib.snprintf(nm, 32, "%%c%d", _MLIR_ConstCount) + # Auto name %c: skip numbers already used by the function's own SSA names (c0/c1/...). + # seq is strictly increasing, so names stay unique; it is decoupled from the table index, and the + # (type,value) cache above still makes identical constants reuse the same name. + seq: int = _MLIR_ConstNameSeq + while seq < MLIR_CONST_CAP and _usedc_get(seq) != 0: + seq += 1 + if seq >= MLIR_CONST_CAP: + MlirFail("MLIR render: no free constant SSA name (%c0..%c511 all used by the function)") + _MLIR_ConstNameSeq = seq + 1 + viperlib.snprintf(nm, 32, "%%c%d", seq) sp2: str* = _mlir_slotp(_MLIR_ConstTy, _MLIR_ConstCount) sv2: str* = _mlir_slotp(_MLIR_ConstVal, _MLIR_ConstCount) sn2: str* = _mlir_slotp(_MLIR_ConstName, _MLIR_ConstCount) @@ -3222,6 +3406,9 @@ def FunctionPrint(buf: str, size: sizet, _MlirTablesInit(pool) _MLIR_Func = func + # Record the function's own SSA names of the form c0/c1/... so the constant namer never picks a + # colliding %c (a user param/local literally named c1 would clash with a materialized const). + _MlirCollectUsedCNames(func) # Clear the previous function's line context: signature/declare and other non-line-driven # positions must not carry an unrelated stale line, avoiding misleading diagnostics. _MLIR_CurLine = None diff --git a/mlirlite/__module.vp b/mlirlite/__module.vp index cb15dc4..4196592 100644 --- a/mlirlite/__module.vp +++ b/mlirlite/__module.vp @@ -15,7 +15,7 @@ from .__types import ( from .__function import ( Function, FunctionPrint, _ll_name_needs_quote, _mlir_read_type, _mlir_read_value, _mlir_ws, - _MlirSymRefAppend, + _MlirSymRefAppend, MlirTablesReset, ) @@ -320,6 +320,11 @@ class LLVMModule: # first use (see _MlirGvEnsureScratch) and never reused across prints (stale pool). MlirGvResetScratch() + # Invalidate the parse-time tables/scratch likewise: they are allocated lazily by the first + # FunctionPrint and reused by the rest of this print, but a print after a pool reset_to must + # reallocate (a pointer into a reclaimed window would dangle). + MlirTablesReset() + _append_cstr(buf, size, "module {\n") # Mode comment (MLIR has no stub/text split) diff --git a/vqt6/__init__.vp b/vqt6/__init__.vp index 2a47c0a..79b3975 100644 --- a/vqt6/__init__.vp +++ b/vqt6/__init__.vp @@ -412,7 +412,7 @@ novirtual class QMenu: def addAction(self, action: t.CPtr): vqt6_qmenu_add_action(self._ptr, action) - def addActionWithText(self, text: str) -> "QAction": + def addActionWithText(self, text: str) -> QAction*: qs: QtStringPtr = vqt6_qstring_create(text) ap: QtActionPtr = vqt6_qmenu_add_action_with_text(self._ptr, qs) vqt6_qstring_free(qs) @@ -420,7 +420,7 @@ novirtual class QMenu: a._ptr = ap return a - def addMenu(self, title: str) -> "QMenu": + def addMenu(self, title: str) -> QMenu*: qs: QtStringPtr = vqt6_qstring_create(title) mp: QtMenuPtr = vqt6_qmenu_add_menu(self._ptr, qs) vqt6_qstring_free(qs) @@ -452,7 +452,7 @@ novirtual class QMenuBar: if self._ptr is None: raise RuntimeError("Failed to create QMenuBar") - def addMenu(self, title: str) -> "QMenu": + def addMenu(self, title: str) -> QMenu*: qs: QtStringPtr = vqt6_qstring_create(title) mp: QtMenuPtr = vqt6_qmenubar_add_menu(self._ptr, qs) vqt6_qstring_free(qs) @@ -1295,7 +1295,7 @@ novirtual class QTreeWidgetItem: def childCount(self) -> int: return vqt6_qtreewidgetitem_child_count(self._ptr) - def child(self, index: int) -> "QTreeWidgetItem": + def child(self, index: int) -> QTreeWidgetItem*: it = QTreeWidgetItem() it._ptr = vqt6_qtreewidgetitem_child(self._ptr, index) return it @@ -1344,12 +1344,12 @@ novirtual class QTreeWidget: def topLevelItemCount(self) -> int: return vqt6_qtreewidget_top_level_item_count(self._ptr) - def topLevelItem(self, index: int) -> "QTreeWidgetItem": + def topLevelItem(self, index: int) -> QTreeWidgetItem*: it = QTreeWidgetItem() it._ptr = vqt6_qtreewidget_top_level_item(self._ptr, index) return it - def currentItem(self) -> "QTreeWidgetItem": + def currentItem(self) -> QTreeWidgetItem*: it = QTreeWidgetItem() it._ptr = vqt6_qtreewidget_current_item(self._ptr) return it