fix(compiler): 修复 ViperApps 暴露的 3 处缺陷(vqt6 返回注解 / MLIR 常量命名 / scratch 复用)
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)。
This commit is contained in:
1 parent
587d989dec
commit
825919b202
3 files changed
+206
-14
No files matched your search
+194
-7
@@ -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<seq>): 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<d> (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<digits>.
|
||||
|
||||
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<digits> 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<digits> 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<digits> in func (params + every intermediate line).
|
||||
|
||||
The constant materializer auto-names constants %c<seq>; 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<seq>: 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<seq> (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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+6
-6
@@ -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
|
||||
|
||||
Reference in new issue
Block a user