把 includes 下 11 个文件(memhub 94、mlirlite/__function 71、ast/parser 54、mlirlite/__module 41、mlirlite/__types 28、ast/lexer 8、ast/tokens 4、sys 2、traceback 1、mlirlite/__builder 1、requests/https 1)的运行时诊断/报错字符串(OOM/MEM/FREEFRAME/MLIR 报错/AST 报错/栈转储横幅等)由中文改为英文。 口径:只改字符串字面量内部文字,行数零变化(+307/-307);printf 格式符与转义序列的多重集合逐一校验不变(_strcheck 异常 0)。验证:三重重编 SUCCESS(184.28s)+ 全量 77/77 顺序回归通过 + VET 编译 SUCCESS / --search-paths 7 条 exit 0。
1595 lines
67 KiB
Plaintext
1595 lines
67 KiB
Plaintext
import t, c
|
||
from stdint import *
|
||
import string
|
||
import stdio
|
||
import stdlib
|
||
import viperlib
|
||
import memhub
|
||
from linkedlist import GSList, GSListNode
|
||
from .__types import (
|
||
LLVMType, TypePrint, Array, PrintStructDefinition, get_struct_name, Struct,
|
||
ParamNode, MlirFail, MlirTypeText, MlirSetContextLine,
|
||
MlirNamedTypesBegin, MlirNamedTypesClear, MlirNamedTypeRegister,
|
||
paramnode_get_ty, paramnode_get_next,
|
||
)
|
||
from .__function import (
|
||
Function, FunctionPrint, _ll_name_needs_quote,
|
||
_mlir_read_type, _mlir_read_value, _mlir_ws,
|
||
_MlirSymRefAppend,
|
||
)
|
||
|
||
|
||
# GlobalVariable: LLVM IR global variable holding name, type, initializer, constant flag
|
||
# and linkage; strong-typed Next via GSListNode[GlobalVariable].
|
||
novirtual class GlobalVariable(GSListNode[GlobalVariable]):
|
||
Name: str # Global name (e.g. ".str" / "my_global")
|
||
Ty: LLVMType* # Variable type
|
||
Initializer: rawptr # Initializer Value* (None = no initializer)
|
||
IsConstant: int # 1=constant, 0=variable
|
||
Linkage: str # Linkage ("private"/"internal"/"external"/...)
|
||
Section: str # Section name (None = default)
|
||
UnnamedAddr: int # 1=unnamed_addr, 2=local_unnamed_addr, 0=none
|
||
Align: int # Alignment in bytes (0 = default; e.g. @c.Attribute(t.attr.aligned(16)))
|
||
|
||
|
||
# NamedTypeNode: named-struct type list node holding an LLVMType* (must be a named Struct);
|
||
# strong-typed Next via GSListNode[NamedTypeNode].
|
||
@t.NoVTable
|
||
class NamedTypeNode(GSListNode[NamedTypeNode]):
|
||
Ty: LLVMType* # Named-struct type pointer
|
||
IsPacked: int # 1 = packed struct (MLIR !llvm.struct<"name", packed (…)>; no field padding)
|
||
|
||
|
||
# DINode: DI metadata node (model kept in sync; ignored at MLIR print time). Debug Info has no
|
||
# MLIR-backend equivalent, so this only records the model (keeping DebugInfoGen compiling) and
|
||
# LLVMModulePrint never emits these nodes (emitting them would break the MLIR syntax).
|
||
@t.NoVTable
|
||
class DINode(GSListNode[DINode]):
|
||
Text: str # node body, e.g. "!DIFile(filename: \"a.vp\", directory: \"/x\")"
|
||
|
||
|
||
# DINamedMeta: named metadata (kept in sync; ignored at MLIR print time)
|
||
@t.NoVTable
|
||
class DINamedMeta(GSListNode[DINamedMeta]):
|
||
Name: str # e.g. "llvm.dbg.cu" / "llvm.module.flags"
|
||
Refs: str # reference list text, e.g. "!1"
|
||
|
||
|
||
# LLVMModule: LLVM IR module container. Uses embedded Head/Tail/Count fields instead of a GSList
|
||
# pointer, avoiding the generic GSList's __sizeof__==0 and vtable-offset mismatch. LLVMModulePrint
|
||
# emits the full .ll content; the class must be LLVMModule (not Module) to avoid clashing with ast.Module.
|
||
@t.NoVTable
|
||
class LLVMModule:
|
||
Name: str # Module name
|
||
FuncHead: Function* # Function list head
|
||
FuncTail: Function* # Function list tail
|
||
FuncCount: sizet # Function count
|
||
GlobalHead: GlobalVariable* # Global list head
|
||
GlobalTail: GlobalVariable* # Global list tail
|
||
GlobalCount: sizet # Global count
|
||
TargetTriple: str # Target triple (e.g. "x86_64-pc-linux-gnu")
|
||
DataLayout: str # Data layout (e.g. "e-m:e-p270:32:32-...")
|
||
NamedTypeHead: NamedTypeNode* # Named-struct type list head
|
||
NamedTypeTail: NamedTypeNode* # Named-struct type list tail
|
||
NamedTypeCount: sizet # Named-struct type count
|
||
DIMetaHead: DINode* # DI metadata node list head (kept in sync; not printed by MLIR)
|
||
DIMetaTail: DINode* # DI metadata node list tail
|
||
DIMetaCount: sizet # DI node count (= next !N id)
|
||
DINamedHead: DINamedMeta* # named metadata list head
|
||
DINamedTail: DINamedMeta* # named metadata list tail
|
||
DINamedCount: sizet # named metadata count
|
||
# Total: 18 fields * 8 = 144 bytes (no vtable; same layout as includes/llvmlite)
|
||
|
||
# Instance methods (objectified: the old module_* top-level functions became methods,
|
||
# kept as one-line delegates).
|
||
|
||
# ---- List operations ----
|
||
def add_function(self, func: Function*):
|
||
"""Append a function to the module's function list (O(1))."""
|
||
if self is None or func is None: return
|
||
func.Next = None
|
||
if self.FuncHead is None:
|
||
self.FuncHead = func
|
||
else:
|
||
self.FuncTail.Next = func
|
||
self.FuncTail = func
|
||
self.FuncCount += 1
|
||
|
||
def add_global(self, gv: GlobalVariable*):
|
||
"""Append a global to the module's global list (O(1))."""
|
||
if self is None or gv is None: return
|
||
gv.Next = None
|
||
if self.GlobalHead is None:
|
||
self.GlobalHead = gv
|
||
else:
|
||
self.GlobalTail.Next = gv
|
||
self.GlobalTail = gv
|
||
self.GlobalCount += 1
|
||
|
||
# ---- Target attributes ----
|
||
def set_target(self, triple: str):
|
||
"""Set the target triple."""
|
||
if self is None: return
|
||
self.TargetTriple = triple
|
||
|
||
def set_datalayout(self, layout: str):
|
||
"""Set the data layout."""
|
||
if self is None: return
|
||
self.DataLayout = layout
|
||
|
||
# ---- DI debug metadata (kept in sync; ignored at MLIR print time, model only) ----
|
||
def add_di_node(self, pool: memhub.MemBuddy*, text: str) -> int:
|
||
"""Append a DI metadata node; return its !N id (0-based, unique), -1 on failure.
|
||
|
||
text is the node body incl. !M references built from earlier ids. MLIR has no DI
|
||
concept, so the node is model-only (LLVMModulePrint does not emit it), keeping
|
||
DebugInfoGen compiling without affecting MLIR output.
|
||
"""
|
||
if self is None or pool is None or text is None:
|
||
return -1
|
||
node: DINode* = pool.alloc(DI_NODE_SIZE)
|
||
if node is None:
|
||
return -1
|
||
string.memset(node, 0, DI_NODE_SIZE)
|
||
# copy body (caller buffer may be reused)
|
||
tlen: sizet = string.strlen(text)
|
||
tcopy: str = pool.alloc(tlen + 1)
|
||
if tcopy is None:
|
||
return -1
|
||
string.strcpy(tcopy, text)
|
||
node.Text = tcopy
|
||
if self.DIMetaHead is None:
|
||
self.DIMetaHead = node
|
||
else:
|
||
self.DIMetaTail.Next = node
|
||
self.DIMetaTail = node
|
||
node_id: int = int(self.DIMetaCount)
|
||
self.DIMetaCount += 1
|
||
return node_id
|
||
|
||
def add_named_metadata(self, pool: memhub.MemBuddy*, name: str, refs: str):
|
||
"""Register named metadata "!<name> = !{ <refs> }"; merge into an existing
|
||
entry with the same name (refs is the brace body, e.g. "!3").
|
||
|
||
MLIR side keeps the model only, no output (LLVMModulePrint does not emit DI).
|
||
"""
|
||
if self is None or pool is None or name is None:
|
||
return
|
||
cur: DINamedMeta* = self.DINamedHead
|
||
cur_steps: sizet = 0
|
||
while cur is not None:
|
||
cur_steps += 1
|
||
if cur_steps > IR_DUMP_CHAIN_LIMIT:
|
||
stdio.printf("[IR-DUMP] list cycle: module di named (DINamedCount=%zu, dump aborted)\n",
|
||
self.DINamedCount)
|
||
stdlib.exit(1)
|
||
if cur.Name is not None and string.strcmp(cur.Name, name) == 0:
|
||
if refs is not None:
|
||
old_len: sizet = 0
|
||
if cur.Refs is not None:
|
||
old_len = string.strlen(cur.Refs)
|
||
merged: str = pool.alloc(old_len + string.strlen(refs) + 3)
|
||
if merged is not None:
|
||
merged[0] = '\0'
|
||
if cur.Refs is not None:
|
||
string.strcat(merged, cur.Refs)
|
||
string.strcat(merged, ", ")
|
||
string.strcat(merged, refs)
|
||
cur.Refs = merged
|
||
return
|
||
cur = cur.Next
|
||
entry: DINamedMeta* = pool.alloc(DI_NAMED_SIZE)
|
||
if entry is None:
|
||
return
|
||
string.memset(entry, 0, DI_NAMED_SIZE)
|
||
ncopy: str = pool.alloc(string.strlen(name) + 1)
|
||
if ncopy is None:
|
||
return
|
||
string.strcpy(ncopy, name)
|
||
entry.Name = ncopy
|
||
if refs is not None:
|
||
rcopy: str = pool.alloc(string.strlen(refs) + 1)
|
||
if rcopy is None:
|
||
return
|
||
string.strcpy(rcopy, refs)
|
||
entry.Refs = rcopy
|
||
if self.DINamedHead is None:
|
||
self.DINamedHead = entry
|
||
else:
|
||
self.DINamedTail.Next = entry
|
||
self.DINamedTail = entry
|
||
self.DINamedCount += 1
|
||
|
||
# ---- Named-struct type management ----
|
||
def add_named_type(self, pool: memhub.MemBuddy*, ty: LLVMType*):
|
||
"""Register a named struct type in the module (MLIR keeps the model only, no output).
|
||
|
||
Non-named structs (None Name or non-Struct) are ignored. Dedup by Name; a full definition
|
||
replaces an opaque declaration of the same name (for cross-module value-type alloca).
|
||
MLIR has no standalone type definition: struct types are inlined at the use site, so this
|
||
emits nothing and NamedTypeHead is model-only, used by the existing APIs.
|
||
"""
|
||
if self is None or pool is None or ty is None:
|
||
return
|
||
name: str = get_struct_name(ty)
|
||
if name is None:
|
||
return
|
||
# Dedup check (a full definition may replace an opaque declaration)
|
||
cur: NamedTypeNode* = self.NamedTypeHead
|
||
cnt_dedup: int = 0
|
||
while cur is not None:
|
||
# Step guard: a cyclic NamedTypeHead list (corruption/insert bug) would hang the
|
||
# traversal forever. Normal lists are far below this cap; exceeding it means the
|
||
# list is corrupt → report and terminate immediately.
|
||
cnt_dedup += 1
|
||
if cnt_dedup > 100000:
|
||
stdio.printf("[ANT-LOOP] NamedTypeHead list suspected cycle, name=%s, visited nodes=%d, forced abort\n",
|
||
name if name is not None else "(null)", cnt_dedup)
|
||
stdlib.exit(1)
|
||
if cur.Ty is not None:
|
||
existing_name: str = get_struct_name(cur.Ty)
|
||
if existing_name is not None:
|
||
if string.strcmp(existing_name, name) == 0:
|
||
# Already a full definition: no replacement needed
|
||
if _is_opaque_struct(cur.Ty) == 0:
|
||
return
|
||
# Existing is opaque and the new type is opaque too: skip
|
||
if _is_opaque_struct(ty) != 0:
|
||
return
|
||
# Replace the opaque declaration with the full definition (patch Ty in place)
|
||
cur.Ty = ty
|
||
return
|
||
cur = cur.Next
|
||
# Create a new node
|
||
node: NamedTypeNode* = pool.alloc(NAMED_TYPE_NODE_SIZE)
|
||
if node is None:
|
||
return
|
||
string.memset(node, 0, NAMED_TYPE_NODE_SIZE)
|
||
node.Ty = ty
|
||
node.IsPacked = 0
|
||
# Insert opaque types at the head so they are declared before definitions that reference
|
||
# them (e.g. HashTable's opaque decl must precede TypeRegistry, or llc reports
|
||
# "use of undefined type"); append full definitions at the tail to keep their order.
|
||
if _is_opaque_struct(ty) != 0:
|
||
node.Next = self.NamedTypeHead
|
||
if self.NamedTypeHead is None:
|
||
self.NamedTypeTail = node
|
||
self.NamedTypeHead = node
|
||
else:
|
||
node.Next = None
|
||
if self.NamedTypeHead is None:
|
||
self.NamedTypeHead = node
|
||
else:
|
||
self.NamedTypeTail.Next = node
|
||
self.NamedTypeTail = node
|
||
self.NamedTypeCount += 1
|
||
|
||
def _is_type_defined_in_module(self, name: str) -> int:
|
||
"""Return 1 if the module already defines a struct with this name, else 0."""
|
||
if self is None or name is None:
|
||
return 0
|
||
cur: NamedTypeNode* = self.NamedTypeHead
|
||
cnt_tdm: int = 0
|
||
while cur is not None:
|
||
# Step guard: as above, prevent infinite traversal of a cyclic NamedTypeHead
|
||
cnt_tdm += 1
|
||
if cnt_tdm > 100000:
|
||
stdio.printf("[TDM-LOOP] NamedTypeHead list suspected cycle, name=%s, forced abort\n",
|
||
name if name is not None else "(null)")
|
||
stdlib.exit(1)
|
||
if cur.Ty is not None:
|
||
existing_name: str = get_struct_name(cur.Ty)
|
||
if existing_name is not None:
|
||
# Fast path: strcmp equality implies equal first chars, so skip strcmp when they differ.
|
||
if existing_name[0] == name[0] and string.strcmp(existing_name, name) == 0:
|
||
return 1
|
||
cur = cur.Next
|
||
return 0
|
||
|
||
def ensure_opaque_for_type(self, pool: memhub.MemBuddy*,
|
||
ty: LLVMType*):
|
||
"""Add opaque declarations for cross-module struct references in a type.
|
||
|
||
When the type references Ptr(Struct(name)) with a '.' in name (SHA1-qualified) and the
|
||
name is not defined in the module, add an opaque declaration.
|
||
"""
|
||
_scan_type_for_cross_module_refs(pool, self, ty)
|
||
|
||
# ---- Printing ----
|
||
def PrintModule(self, buf: str, size: sizet,
|
||
pool: memhub.MemBuddy*, mode: int):
|
||
"""Serialize the module to MLIR (llvm dialect) text into buf.
|
||
|
||
Skeleton: module { <globals> <functions> }. MLIR has no stub/text split, so a comment
|
||
marks the mode: OUTPUT_FULL → all; OUTPUT_STUB → global/function declarations only;
|
||
OUTPUT_TEXT → body-bearing functions only. target triple / datalayout are recorded but
|
||
not emitted (not part of an MLIR module).
|
||
"""
|
||
if self is None or buf is None or size == 0: return
|
||
|
||
# Clear the line context: the global section is not line-driven, so a type error here
|
||
# must not carry a stale line from a previous function (_MlirCtxLine).
|
||
MlirSetContextLine(None)
|
||
|
||
# Publish this module's named-struct registry: name-only type text (%"name") resolves
|
||
# through it to the full definition, expanded inline (!llvm.struct<"name", (…)>). The
|
||
# table is rebuilt per print and cleared at the end, so its pointers are valid only then.
|
||
_MlirPublishNamedTypes(self, pool)
|
||
|
||
# Invalidate the region-render scratch: it is rebuilt lazily for the current pool on
|
||
# first use (see _MlirGvEnsureScratch) and never reused across prints (stale pool).
|
||
MlirGvResetScratch()
|
||
|
||
_append_cstr(buf, size, "module {\n")
|
||
|
||
# Mode comment (MLIR has no stub/text split)
|
||
if mode == OUTPUT_STUB:
|
||
_append_cstr(buf, size, " // MLIR-STUB: global and function declarations only\n")
|
||
elif mode == OUTPUT_TEXT:
|
||
_append_cstr(buf, size, " // MLIR-TEXT: body-bearing functions and initialized globals only\n")
|
||
# target triple / datalayout: not carried by an MLIR module; supplied at mlir-translate/llc stage
|
||
if self.TargetTriple is not None or self.DataLayout is not None:
|
||
_append_cstr(buf, size,
|
||
" // target triple / datalayout are supplied by the mlir-translate / llc stage\n")
|
||
|
||
# Line-indent scratch buffer (module members indent 2 spaces). Its cap is bounded by the
|
||
# module pool's buddy limit: a ~2 MiB sub-pool (max_order=15) allows max 1 MiB per block,
|
||
# minus a 16-byte header ⇒ 1048560. MLIR text is long (inlined named-struct bodies), so use
|
||
# the largest single block the pool can serve; overflow is caught and fails loud.
|
||
tmp_cap: sizet = 1048560
|
||
tmp: str = pool.alloc(tmp_cap)
|
||
if tmp is None:
|
||
MlirFail("MLIR render: module indent scratch buffer allocation failed (1048560 ~= 1 MiB - 16-byte block header)")
|
||
|
||
# Globals (MLIR has no standalone type-definition line: named structs are inlined at the
|
||
# use site, so NamedTypeHead definitions are not emitted here, only registered for lookup).
|
||
gv: GlobalVariable* = self.GlobalHead
|
||
gv_steps: sizet = 0
|
||
while gv is not None:
|
||
gv_steps += 1
|
||
if gv_steps > IR_DUMP_CHAIN_LIMIT:
|
||
stdio.printf("[IR-DUMP] list cycle: module globals (GlobalCount=%zu, dump aborted)\n",
|
||
self.GlobalCount)
|
||
stdlib.exit(1)
|
||
if mode == OUTPUT_TEXT and gv.Initializer is None:
|
||
# text mode skips globals with no initializer (pure declarations)
|
||
gv = gv.Next
|
||
continue
|
||
if tmp is not None:
|
||
tmp[0] = '\0'
|
||
if mode == OUTPUT_STUB:
|
||
_MlirGlobalLine(tmp, tmp_cap, pool, gv, 1)
|
||
else:
|
||
_MlirGlobalLine(tmp, tmp_cap, pool, gv, 0)
|
||
_MlirAppendIndented(buf, size, tmp, 2, pool)
|
||
gv = gv.Next
|
||
|
||
# Walk functions (filter declare/define by mode)
|
||
func: Function* = self.FuncHead
|
||
func_steps: sizet = 0
|
||
while func is not None:
|
||
func_steps += 1
|
||
if func_steps > IR_DUMP_CHAIN_LIMIT:
|
||
stdio.printf("[IR-DUMP] list cycle: module funcs print (FuncCount=%zu, dump aborted)\n",
|
||
self.FuncCount)
|
||
stdlib.exit(1)
|
||
emit: int = 0
|
||
if mode == OUTPUT_STUB:
|
||
if func.IsDeclared == 1:
|
||
emit = 1
|
||
elif mode == OUTPUT_TEXT:
|
||
if func.IsDeclared == 0:
|
||
emit = 1
|
||
else:
|
||
emit = 1
|
||
if emit != 0 and tmp is not None:
|
||
tmp[0] = '\0'
|
||
FunctionPrint(tmp, tmp_cap, func, pool)
|
||
_MlirAppendIndented(buf, size, tmp, 2, pool)
|
||
func = func.Next
|
||
|
||
_append_cstr(buf, size, "}\n")
|
||
# Print done: clear the named-struct registry (its type pointers are no longer dereferenced)
|
||
MlirNamedTypesClear()
|
||
|
||
|
||
# _MlirPublishNamedTypes - publish the module's named-struct list to the __types.vp registry.
|
||
# Name-only type text (%"sha1.Cls") must expand to the inline named body, so this publishes a
|
||
# name → full-definition lookup (opaque entries too, so they report "still opaque", not
|
||
# "unregistered") plus each node's packed flag (NamedTypeNode.IsPacked) for the render side.
|
||
def _MlirPublishNamedTypes(mod: LLVMModule*, pool: memhub.MemBuddy*):
|
||
"""Rebuild the named-struct registry from NamedTypeHead (print-time lookup, incl. packed).
|
||
|
||
Count the real list length first (not trusting NamedTypeCount), then allocate exactly that
|
||
many slots and register each; real modules can have thousands of named types, so no fixed
|
||
capacity is assumed; nothing is allocated when there are none.
|
||
"""
|
||
if mod is None or pool is None:
|
||
return
|
||
cnt: sizet = 0
|
||
nt: NamedTypeNode* = mod.NamedTypeHead
|
||
nt_steps: sizet = 0
|
||
while nt is not None:
|
||
nt_steps += 1
|
||
if nt_steps > IR_DUMP_CHAIN_LIMIT:
|
||
stdio.printf("[IR-DUMP] list cycle: module named types count (NamedTypeCount=%zu, count aborted)\n",
|
||
mod.NamedTypeCount)
|
||
stdlib.exit(1)
|
||
cnt += 1
|
||
nt = nt.Next
|
||
MlirNamedTypesBegin(pool, cnt)
|
||
nt = mod.NamedTypeHead
|
||
nt_steps = 0
|
||
while nt is not None:
|
||
nt_steps += 1
|
||
if nt_steps > IR_DUMP_CHAIN_LIMIT:
|
||
stdio.printf("[IR-DUMP] list cycle: module named types publish (NamedTypeCount=%zu, publish aborted)\n",
|
||
mod.NamedTypeCount)
|
||
stdlib.exit(1)
|
||
if nt.Ty is not None:
|
||
# The packed flag lives only on NamedTypeNode (LLVMType.Struct lacks it), set only by
|
||
# module_set_named_type_packed; register it together with the type here.
|
||
MlirNamedTypeRegister(nt.Ty, nt.IsPacked)
|
||
nt = nt.Next
|
||
|
||
|
||
# Factory functions
|
||
def new_module(pool: memhub.MemBuddy*, name: str) -> LLVMModule*:
|
||
"""Create a module."""
|
||
# Hardcoded 144 bytes: 18 fields * 8 bytes (no vtable, @t.NoVTable), incl. the DI fields
|
||
# (DIMetaHead/Tail/Count, DINamedHead/Tail/Count), same layout as llvmlite.
|
||
ptr: LLVMModule* = pool.alloc(144)
|
||
if ptr is None: return None
|
||
string.memset(ptr, 0, 144)
|
||
ptr.Name = name
|
||
ptr.FuncHead = None
|
||
ptr.FuncTail = None
|
||
ptr.FuncCount = 0
|
||
ptr.GlobalHead = None
|
||
ptr.GlobalTail = None
|
||
ptr.GlobalCount = 0
|
||
ptr.TargetTriple = None
|
||
ptr.DataLayout = None
|
||
ptr.NamedTypeHead = None
|
||
ptr.NamedTypeTail = None
|
||
ptr.NamedTypeCount = 0
|
||
ptr.DIMetaHead = None
|
||
ptr.DIMetaTail = None
|
||
ptr.DIMetaCount = 0
|
||
ptr.DINamedHead = None
|
||
ptr.DINamedTail = None
|
||
ptr.DINamedCount = 0
|
||
return ptr
|
||
|
||
|
||
# GlobalVariable hardcoded size: Next(8)+Name(8)+Ty(8)+Initializer(8)+IsConstant(4)+Linkage(8)+Section(8)+UnnamedAddr(4)+Align(4) = 72 bytes
|
||
define GV_SIZE = 72
|
||
|
||
# NamedTypeNode hardcoded size: Next(8) + Ty(8) + IsPacked(8) = 24 bytes
|
||
define NAMED_TYPE_NODE_SIZE = 24
|
||
|
||
# DINode hardcoded size: Next(8) + Text(8) = 16 bytes (same as llvmlite)
|
||
define DI_NODE_SIZE = 16
|
||
|
||
# DINamedMeta hardcoded size: Next(8) + Name(8) + Refs(8) = 24 bytes (same as llvmlite)
|
||
define DI_NAMED_SIZE = 24
|
||
|
||
def new_global_variable(pool: memhub.MemBuddy*,
|
||
name: str, ty: LLVMType*) -> GlobalVariable*:
|
||
"""Create a global variable."""
|
||
ptr: GlobalVariable* = pool.alloc(GV_SIZE)
|
||
if ptr is None: return None
|
||
string.memset(ptr, 0, GV_SIZE)
|
||
ptr.Name = name
|
||
ptr.Ty = ty
|
||
ptr.Initializer = None
|
||
ptr.IsConstant = 0
|
||
ptr.Linkage = "external"
|
||
ptr.Section = None
|
||
ptr.UnnamedAddr = 0
|
||
ptr.Align = 0
|
||
return ptr
|
||
|
||
|
||
# List operations (embedded Head/Tail/Count, O(1) append)
|
||
def module_add_function(mod: LLVMModule*, func: Function*):
|
||
mod.add_function(func)
|
||
|
||
|
||
def module_get_func_head(mod: LLVMModule*) -> Function*:
|
||
"""Return the module's function list head (cross-module accessor bypassing the stub-field trap)."""
|
||
if mod is None:
|
||
return None
|
||
return mod.FuncHead
|
||
|
||
|
||
def module_add_global(mod: LLVMModule*, gv: GlobalVariable*):
|
||
mod.add_global(gv)
|
||
|
||
|
||
def module_set_target(mod: LLVMModule*, triple: str):
|
||
mod.set_target(triple)
|
||
|
||
|
||
def module_set_datalayout(mod: LLVMModule*, layout: str):
|
||
mod.set_datalayout(layout)
|
||
|
||
|
||
# DI debug metadata (kept in sync; ignored at MLIR print time, model only)
|
||
def module_add_di_node(mod: LLVMModule*, pool: memhub.MemBuddy*,
|
||
text: str) -> int:
|
||
"""Append a DI metadata node; return its !N id (-1 on failure)."""
|
||
return mod.add_di_node(pool, text)
|
||
|
||
|
||
def module_add_named_metadata(mod: LLVMModule*, pool: memhub.MemBuddy*,
|
||
name: str, refs: str):
|
||
"""Register named metadata "!<name> = !{ <refs> }"."""
|
||
mod.add_named_metadata(pool, name, refs)
|
||
|
||
|
||
# Named-struct type management
|
||
|
||
# _is_opaque_struct - check whether a type is an opaque struct.
|
||
# An opaque struct is a Struct with fcount == -1 (sentinel for a cross-module forward declaration,
|
||
# name only), created by Struct(pool, None, -1, name). Must be distinguished from an empty struct
|
||
# (fcount==0, fields==None), which is a valid full definition ({} type, allocatable).
|
||
# Returns: 1 = opaque / 0 = not.
|
||
def _is_opaque_struct(ty: LLVMType*) -> int:
|
||
"""Return 1 if the type is an opaque struct, else 0.
|
||
|
||
opaque is a cross-module forward declaration (name only) created by
|
||
_scan_type_for_cross_module_refs, marked by fcount=-1. An empty struct (fcount==0,
|
||
fields==None) is a valid full definition (LLVM {} type, allocatable) and must be kept
|
||
distinct from opaque, or alloca fails with Cannot allocate unsized type.
|
||
"""
|
||
if ty is None:
|
||
return 0
|
||
match ty:
|
||
case LLVMType.Struct(fields, fcount, name):
|
||
# opaque: fcount == -1 (sentinel)
|
||
if fcount < 0:
|
||
return 1
|
||
return 0
|
||
case _:
|
||
return 0
|
||
|
||
|
||
def module_add_named_type(mod: LLVMModule*, pool: memhub.MemBuddy*,
|
||
ty: LLVMType*):
|
||
mod.add_named_type(pool, ty)
|
||
|
||
|
||
def module_set_named_type_packed(mod: LLVMModule*, pool: memhub.MemBuddy*,
|
||
ty: LLVMType*, is_packed: int):
|
||
"""Set a named struct's packed flag by name (MLIR emits !llvm.struct<"name", packed (…)>).
|
||
|
||
Called after parsing @c.Attribute(t.attr.packed) so llc computes field offsets/GEP with the
|
||
packed layout. MLIR carries the flag via the print-time registry (read from
|
||
NamedTypeNode.IsPacked) and adds the `packed ` keyword at the use site. ty must be a named
|
||
Struct (located by get_struct_name); not found is skipped silently. Anonymous structs return
|
||
immediately — the flag has no place on them.
|
||
"""
|
||
if mod is None or ty is None:
|
||
return
|
||
name: str = get_struct_name(ty)
|
||
if name is None:
|
||
return
|
||
cur: NamedTypeNode* = mod.NamedTypeHead
|
||
cnt_p: int = 0
|
||
while cur is not None:
|
||
cnt_p += 1
|
||
if cnt_p > 100000:
|
||
return
|
||
if cur.Ty is not None:
|
||
existing_name: str = get_struct_name(cur.Ty)
|
||
if existing_name is not None and string.strcmp(existing_name, name) == 0:
|
||
cur.IsPacked = is_packed
|
||
return
|
||
cur = cur.Next
|
||
|
||
|
||
# module_ensure_opaque_for_type - add opaque declarations for cross-module struct references.
|
||
# Recursively scans the LLVMType tree; for a Ptr(Struct(name)) whose '.'-containing (SHA1-qualified)
|
||
# name is undefined in the module, adds an opaque declaration, fixing llc "use of undefined type".
|
||
def _is_type_defined_in_module(mod: LLVMModule*, name: str) -> int:
|
||
return mod._is_type_defined_in_module(name)
|
||
|
||
|
||
def _scan_type_for_cross_module_refs(pool: memhub.MemBuddy*,
|
||
mod: LLVMModule*,
|
||
ty: LLVMType*,
|
||
depth: int = 0):
|
||
"""Recursively scan the type tree and add opaque declarations for cross-module struct refs."""
|
||
if ty is None or mod is None or pool is None:
|
||
return
|
||
# Depth guard: a self-referential struct (list/tree node, e.g. Node{...; Ptr(Node) next})
|
||
# would recurse forever; cap the depth like other recursions (TypePrint/DeepCopyType).
|
||
if depth >= 100:
|
||
return
|
||
match ty:
|
||
case LLVMType.Ptr(pointee):
|
||
if pointee is not None:
|
||
match pointee:
|
||
case LLVMType.Struct(sfields, sfcount, sname):
|
||
if sname is not None:
|
||
# Check for a cross-module reference (name contains '.')
|
||
if string.strchr(sname, 46) is not None:
|
||
# Check whether it is already defined in the module
|
||
if _is_type_defined_in_module(mod, sname) == 0:
|
||
# Create an opaque type and add it to the module; fcount=-1 marks
|
||
# opaque (vs. empty {} struct, whose fcount==0 is a valid
|
||
# full definition and can be alloc'd).
|
||
opaque_ty: LLVMType* = Struct(pool, None, -1, sname)
|
||
if opaque_ty is not None:
|
||
module_add_named_type(mod, pool, opaque_ty)
|
||
# Recurse into struct fields for cross-module refs
|
||
# (e.g. HashTable in TypeRegistry{HashTable*, MemManager*}).
|
||
if sfields is not None:
|
||
fld: ParamNode* = sfields
|
||
fld_steps: sizet = 0
|
||
while fld is not None:
|
||
fld_steps += 1
|
||
if fld_steps > IR_DUMP_CHAIN_LIMIT:
|
||
stdio.printf("[IR-DUMP] list cycle: struct fields (sname=%s, dump aborted)\n",
|
||
sname)
|
||
stdlib.exit(1)
|
||
if fld.Ty is not None:
|
||
_scan_type_for_cross_module_refs(pool, mod, fld.Ty, depth + 1)
|
||
fld = fld.Next
|
||
case _:
|
||
_scan_type_for_cross_module_refs(pool, mod, pointee, depth + 1)
|
||
case LLVMType.Struct(sfields2, sfcount2, sname2):
|
||
# Top-level struct (non-pointer): recurse into fields
|
||
if sfields2 is not None:
|
||
fld2: ParamNode* = sfields2
|
||
fld2_steps: sizet = 0
|
||
while fld2 is not None:
|
||
fld2_steps += 1
|
||
if fld2_steps > IR_DUMP_CHAIN_LIMIT:
|
||
stdio.printf("[IR-DUMP] list cycle: struct fields2 (sname=%s, dump aborted)\n",
|
||
sname2)
|
||
stdlib.exit(1)
|
||
if fld2.Ty is not None:
|
||
_scan_type_for_cross_module_refs(pool, mod, fld2.Ty, depth + 1)
|
||
fld2 = fld2.Next
|
||
case LLVMType.Array(elem_ty, acount):
|
||
if elem_ty is not None:
|
||
_scan_type_for_cross_module_refs(pool, mod, elem_ty, depth + 1)
|
||
case _:
|
||
pass
|
||
|
||
|
||
def module_ensure_opaque_for_type(mod: LLVMModule*,
|
||
pool: memhub.MemBuddy*,
|
||
ty: LLVMType*):
|
||
mod.ensure_opaque_for_type(pool, ty)
|
||
|
||
|
||
# Global variable attribute setters
|
||
def global_set_constant(gv: GlobalVariable*, is_const: int):
|
||
"""Set whether the global is a constant."""
|
||
if gv is None: return
|
||
gv.IsConstant = is_const
|
||
|
||
|
||
def global_set_linkage(gv: GlobalVariable*, linkage: str):
|
||
"""Set the linkage (private/internal/external/...)."""
|
||
if gv is None: return
|
||
gv.Linkage = linkage
|
||
|
||
|
||
def global_set_unnamed_addr(gv: GlobalVariable*, level: int):
|
||
"""Set the unnamed_addr attribute (0=none, 1=unnamed_addr, 2=local_unnamed_addr)."""
|
||
if gv is None: return
|
||
gv.UnnamedAddr = level
|
||
|
||
|
||
def global_set_section(gv: GlobalVariable*, section: str):
|
||
"""Set the section name."""
|
||
if gv is None: return
|
||
gv.Section = section
|
||
|
||
|
||
# Output mode constants
|
||
define OUTPUT_FULL = 0# Full IR (stub + text)
|
||
define OUTPUT_STUB = 1# Declarations only (header + type defs + declare + external global)
|
||
define OUTPUT_TEXT = 2# Code only (define + global with initializer)
|
||
|
||
# dump sentinel: max traversal steps for IR lists (cycle detection, fail-fast). If any chain's
|
||
# Next is corrupted into a cycle, traversal would hang tpv.exe; exceeding the cap prints the list
|
||
# name and exits(1), turning a hang into a deterministic error (like memhub.self_check max_steps).
|
||
define IR_DUMP_CHAIN_LIMIT = 100000
|
||
_MlirModOutTrunc: int = 0
|
||
|
||
# LLVMModulePrint: serialize the module to .ll text; mode: OUTPUT_FULL / OUTPUT_STUB / OUTPUT_TEXT.
|
||
def LLVMModulePrint(buf: str, size: sizet,
|
||
mod: LLVMModule*, pool: memhub.MemBuddy*,
|
||
mode: int):
|
||
global _MlirModOutTrunc
|
||
_MlirModOutTrunc = 0
|
||
mod.PrintModule(buf, size, pool, mode)
|
||
if _MlirModOutTrunc != 0:
|
||
_MlirModOutTrunc = 0
|
||
MlirFail("MLIR render: module text exceeds output buffer and was truncated (increase the dump_ir buffer)")
|
||
|
||
|
||
# Global variable rendering (MLIR); linkage/constant/unnamed_addr/section map to keywords/attributes.
|
||
# Initializers take two paths (see _MlirGlobalInitNeedsRegion): attribute path (integer/float
|
||
# literals, cstring, string literal, pure scalar array) or region path (root-level bare null/
|
||
# zeroinitializer, struct/vtable, symbol-referencing aggregates, pointer initializers → empty value
|
||
# parens + initializer region). Any form neither path covers fails loud via MlirFail.
|
||
def _MlirGlobalLine(buf: str, size: sizet,
|
||
pool: memhub.MemBuddy*, gv: GlobalVariable*,
|
||
stub: int):
|
||
"""Render one llvm.mlir.global (stub=1 emits the external declaration form)."""
|
||
if gv is None:
|
||
return
|
||
_append_cstr(buf, size, "llvm.mlir.global ")
|
||
# Linkage
|
||
if stub != 0:
|
||
_append_cstr(buf, size, "external ")
|
||
elif gv.Linkage is not None:
|
||
# external + initializer is illegal IR, so drop external
|
||
if gv.Initializer is not None and string.strcmp(gv.Linkage, "external") == 0:
|
||
pass
|
||
else:
|
||
_append_cstr(buf, size, gv.Linkage)
|
||
_append_cstr(buf, size, " ")
|
||
if stub == 0:
|
||
# unnamed_addr
|
||
if gv.UnnamedAddr == 1:
|
||
_append_cstr(buf, size, "unnamed_addr ")
|
||
elif gv.UnnamedAddr == 2:
|
||
_append_cstr(buf, size, "local_unnamed_addr ")
|
||
# constant / plain global (non-constant needs no keyword in MLIR)
|
||
if gv.IsConstant == 1:
|
||
_append_cstr(buf, size, "constant ")
|
||
# Name
|
||
_append_cstr(buf, size, "@")
|
||
if gv.Name is not None:
|
||
if _ll_name_needs_quote(gv.Name) != 0:
|
||
_append_cstr(buf, size, "\"")
|
||
_append_cstr(buf, size, gv.Name)
|
||
_append_cstr(buf, size, "\"")
|
||
else:
|
||
_append_cstr(buf, size, gv.Name)
|
||
# Initializer: aggregates / symbol references / root-level bare zero use the region path
|
||
# (see _MlirGlobalInitNeedsRegion).
|
||
region: int = 0
|
||
if stub == 0 and gv.Initializer is not None:
|
||
region = _MlirGlobalInitNeedsRegion(gv.Initializer)
|
||
if region == 0:
|
||
_append_cstr(buf, size, "(")
|
||
_MlirGlobalInit(buf, size, pool, gv)
|
||
_append_cstr(buf, size, ")")
|
||
else:
|
||
# Region form: empty value parens (the initializer is built by insertvalue in the region)
|
||
_append_cstr(buf, size, "()")
|
||
else:
|
||
# No initializer (stub=1 external decl / global without one): the value parens MUST be kept
|
||
# — mlir-opt 22.1.8 rejects `@x : i32` ("expected '('") but accepts `@x() : i32`.
|
||
_append_cstr(buf, size, "()")
|
||
# Attribute dict (section / alignment) MUST be emitted before the type (after the value);
|
||
# mlir-opt 22.1.8 fails to parse otherwise (a dict after the type is read as a new op).
|
||
# `unnamed_addr` is a keyword (emitted before the name) and must not go in the dict.
|
||
if stub == 0 and (gv.Section is not None or gv.Align > 0):
|
||
_append_cstr(buf, size, " {")
|
||
if gv.Section is not None:
|
||
_append_cstr(buf, size, "section = \"")
|
||
_append_cstr(buf, size, gv.Section)
|
||
_append_cstr(buf, size, "\"")
|
||
if gv.Align > 0:
|
||
_append_cstr(buf, size, ", ")
|
||
if gv.Align > 0:
|
||
ab: str = pool.alloc(48)
|
||
if ab is not None:
|
||
viperlib.snprintf(ab, 48, "alignment = %d : i64", gv.Align)
|
||
_append_cstr(buf, size, ab)
|
||
_append_cstr(buf, size, "}")
|
||
# Type
|
||
_append_cstr(buf, size, " : ")
|
||
ty_buf: str = pool.alloc(4096)
|
||
if ty_buf is not None:
|
||
ty_buf[0] = '\0'
|
||
TypePrint(ty_buf, 4096, gv.Ty, pool)
|
||
_append_cstr(buf, size, ty_buf)
|
||
# initializer region (top-level aggregate/pointer initializer: leaf op + undef + insertvalue chain + return)
|
||
if region != 0:
|
||
_append_cstr(buf, size, " {\n")
|
||
_MlirGlobalInitRegion(buf, size, pool, gv)
|
||
_append_cstr(buf, size, "}")
|
||
_append_cstr(buf, size, "\n")
|
||
|
||
|
||
def _MlirGlobalInit(buf: str, size: sizet,
|
||
pool: memhub.MemBuddy*, gv: GlobalVariable*):
|
||
"""Render a global initializer (MLIR attribute text)."""
|
||
x: str = gv.Initializer
|
||
if x is None:
|
||
return
|
||
if x[0] == 'c' and x[1] == '"':
|
||
_MlirCStringToMlir(buf, size, x, pool)
|
||
return
|
||
if x[0] == '"':
|
||
# Already a string literal
|
||
_append_cstr(buf, size, x)
|
||
return
|
||
if x[0] == '[':
|
||
# Integer/float array: [i8 1, i8 2] → [1 : i8, 2 : i8]. mlir-opt 22.1.8 rejects
|
||
# dense<[...]> (dense only takes builtin shaped types) and untyped elements default to
|
||
# i64; the correct form is the array attribute in the value parens with per-element
|
||
# `value : type` annotations. The global type is emitted after the parens by _MlirGlobalLine.
|
||
_MlirArrayDenseAt(buf, size, x, pool)
|
||
return
|
||
if string.strcmp(x, "null") == 0 or string.strcmp(x, "zeroinitializer") == 0:
|
||
# Root-level bare zero now goes through the region path (_MlirGlobalInitNeedsRegion returns
|
||
# 1); this attribute fallback is kept only as a safety net (mlir-opt rejects `(zeroinitializer)`).
|
||
_append_cstr(buf, size, "zeroinitializer")
|
||
return
|
||
if x[0] == '@':
|
||
# Symbol-reference initializer (top-level @name uses the region path; this is the fallback)
|
||
_append_cstr(buf, size, x)
|
||
return
|
||
c0: int = x[0]
|
||
if c0 == '-' or (c0 >= '0' and c0 <= '9'):
|
||
# Integer/float literal: "<text> : <MLIR type>"
|
||
_append_cstr(buf, size, x)
|
||
_append_cstr(buf, size, " : ")
|
||
ty_buf: str = pool.alloc(4096)
|
||
if ty_buf is not None:
|
||
ty_buf[0] = '\0'
|
||
TypePrint(ty_buf, 4096, gv.Ty, pool)
|
||
_append_cstr(buf, size, ty_buf)
|
||
return
|
||
# Residual form not covered by the attribute path and not routed to region: fail loud, never
|
||
# emit wrong MLIR (structs/vtables, symbol-referencing aggregates and pointer initializers are
|
||
# already handled by _MlirGlobalInitRegion on the region path).
|
||
MlirFail("MLIR render: global initializer form unsupported in v1 (neither the attribute path nor the initializer region covers it)")
|
||
|
||
|
||
def _MlirCStringToMlir(buf: str, size: sizet, src: str, pool: memhub.MemBuddy*):
|
||
"""c"hello\\00" → "hello\\00" (drop only the 'c' prefix; keep escapes verbatim).
|
||
|
||
mlir-opt 22.1.8 requires the string attribute length to equal the declared i8 array length.
|
||
TPV string constants include a trailing NUL ([N+1 x i8]), so \\00 MUST be kept — stripping it
|
||
would drop the C terminator and break the ABI.
|
||
"""
|
||
p: str = src + 1 # Skip 'c', point at '"'
|
||
if p[0] != '"':
|
||
MlirFail("MLIR render: illegal global c-string initializer form")
|
||
_append_cstr(buf, size, p)
|
||
|
||
|
||
def _MlirArrayDenseAt(buf: str, size: sizet, src: str,
|
||
pool: memhub.MemBuddy*) -> str:
|
||
"""LLVM array literal → MLIR dense content (nested recursive); returns the new cursor."""
|
||
if src is None or src[0] != '[':
|
||
MlirFail("MLIR render: global array initializer form unsupported in v1")
|
||
p: str = src + 1
|
||
_append_cstr(buf, size, "[")
|
||
n: int = 0
|
||
g: int = 0
|
||
while p[0] != '\0':
|
||
g += 1
|
||
if g > 4096:
|
||
MlirFail("MLIR render: too many global array elements")
|
||
p = _mlir_ws(p)
|
||
if p[0] == ']':
|
||
p = p + 1
|
||
break
|
||
if p[0] == '\0':
|
||
break
|
||
if n > 0:
|
||
_append_cstr(buf, size, ", ")
|
||
if p[0] == '[':
|
||
p = _MlirArrayDenseAt(buf, size, p, pool)
|
||
else:
|
||
tb: str = pool.alloc(512)
|
||
if tb is None:
|
||
MlirFail("MLIR render: array element type buffer allocation failed")
|
||
p = _mlir_read_type(p, tb, 512)
|
||
p = _mlir_ws(p)
|
||
vb: str = pool.alloc(512)
|
||
if vb is None:
|
||
MlirFail("MLIR render: array element value buffer allocation failed")
|
||
p = _mlir_read_value(p, vb, 512)
|
||
_append_cstr(buf, size, vb)
|
||
# Elements must carry a type annotation: MLIR array attributes default to i64, so
|
||
# i8/i32/pointer elements would mismatch the global type (working form:
|
||
# `[1 : i8, 2 : i8] : !llvm.array<2 x i8>`).
|
||
_append_cstr(buf, size, " : ")
|
||
mtb: str = pool.alloc(4096)
|
||
if mtb is None:
|
||
MlirFail("MLIR render: array element MLIR type buffer allocation failed")
|
||
mtb[0] = '\0'
|
||
MlirTypeText(mtb, 4096, tb, pool)
|
||
_append_cstr(buf, size, mtb)
|
||
n += 1
|
||
p = _mlir_ws(p)
|
||
if p[0] == ',':
|
||
p = p + 1
|
||
_append_cstr(buf, size, "]")
|
||
return p
|
||
|
||
|
||
# Global initializer region rendering (struct/vtable aggregates and pointer initializers): leaves
|
||
# (addressof/zero/constant) first, then undef + insertvalue chain, whose type suffix is always the
|
||
# top-level aggregate and whose index path comes from a parallel initializer/type walk. Element MLIR
|
||
# types come from the global type tree via TypePrint; constexpr bitcast/GEP are treated as symbol
|
||
# references; unsupported leaves fail loud.
|
||
define GV_REGION_BUF_SIZE = 32768
|
||
|
||
# Index-path reuse stack: one slot per recursion depth (top-level path="" → depth d in slot d).
|
||
# Depth cap matches _MlirGvWalk's 64-level guard with margin (72); 512 bytes per slot suffices.
|
||
define GV_PATH_SLOT = 512
|
||
define GV_PATH_MAX = 72
|
||
|
||
# Region-render state (single-threaded recursive render, reset per global)
|
||
_MlirGvLeafBuf: str = None # Leaf op buffer
|
||
_MlirGvInsBuf: str = None # insertvalue chain buffer
|
||
_MlirGvTopType: str = None # Top-level aggregate MLIR type text
|
||
_MlirGvLast: str = None # SSA name of the current node
|
||
_MlirGvPrev: str = None # SSA name of the previous insertvalue result
|
||
_MlirGvPool: memhub.MemBuddy* = None
|
||
_MlirGvLeafSeq: int = 0
|
||
_MlirGvInsSeq: int = 0
|
||
|
||
# ---- Region-render REUSED scratch buffers (lazily allocated once, shared across globals/nodes) ----
|
||
# Allocating new buffers per global/node exhausted the ~2 MiB sub-pool for struct/vtable-heavy
|
||
# modules; the render is single-threaded and sequential, so one allocation reused is enough.
|
||
_MlirGvSbRead1: str = None # 512B: type-prefix token read scratch
|
||
_MlirGvSbRead2: str = None # 512B: leaf type-token read scratch
|
||
_MlirGvSbName: str = None # 512B: symbol-name / constexpr symbol-name scratch
|
||
_MlirGvSbVal: str = None # 512B: scalar initializer literal scratch
|
||
_MlirGvTyBuf: str = None # 4096B: element MLIR type text (zero/const)
|
||
_MlirGvNameBuf: str = None # 32B: SSA name generation scratch
|
||
_MlirGvPathStack: str = None # GV_PATH_MAX × GV_PATH_SLOT: index-path reuse stack
|
||
|
||
|
||
def MlirGvResetScratch():
|
||
"""Invalidate the region-render scratch so EnsureScratch reallocates from the current pool.
|
||
|
||
The scratch is reused only within a single PrintModule (shared across globals/nodes); clearing
|
||
it at print end prevents reusing a previous print's pointers (freed or reused pool → dangling).
|
||
"""
|
||
global _MlirGvLeafBuf
|
||
_MlirGvLeafBuf = None
|
||
|
||
|
||
def _MlirGvEnsureScratch(pool: memhub.MemBuddy*) -> int:
|
||
"""Lazily allocate this PrintModule's reused scratch buffers (shared across globals/nodes); returns 1 when ready."""
|
||
global _MlirGvLeafBuf, _MlirGvInsBuf, _MlirGvTopType, _MlirGvLast, _MlirGvPrev
|
||
global _MlirGvSbRead1, _MlirGvSbRead2, _MlirGvSbName, _MlirGvSbVal
|
||
global _MlirGvTyBuf, _MlirGvNameBuf, _MlirGvPathStack
|
||
if _MlirGvLeafBuf is not None:
|
||
return 1
|
||
lb: str = pool.alloc(GV_REGION_BUF_SIZE)
|
||
ib: str = pool.alloc(GV_REGION_BUF_SIZE)
|
||
tb: str = pool.alloc(4096)
|
||
la: str = pool.alloc(32)
|
||
pr: str = pool.alloc(32)
|
||
r1: str = pool.alloc(512)
|
||
r2: str = pool.alloc(512)
|
||
nm: str = pool.alloc(512)
|
||
vl: str = pool.alloc(512)
|
||
tyb: str = pool.alloc(4096)
|
||
nb: str = pool.alloc(32)
|
||
ps: str = pool.alloc(GV_PATH_SLOT * GV_PATH_MAX)
|
||
if lb is None or ib is None or tb is None or la is None or pr is None \
|
||
or r1 is None or r2 is None or nm is None or vl is None \
|
||
or tyb is None or nb is None or ps is None:
|
||
MlirFail("MLIR render: global region buffer allocation failed")
|
||
lb[0] = '\0'; ib[0] = '\0'; tb[0] = '\0'; la[0] = '\0'; pr[0] = '\0'
|
||
r1[0] = '\0'; r2[0] = '\0'; nm[0] = '\0'; vl[0] = '\0'
|
||
tyb[0] = '\0'; nb[0] = '\0'; ps[0] = '\0'
|
||
_MlirGvLeafBuf = lb
|
||
_MlirGvInsBuf = ib
|
||
_MlirGvTopType = tb
|
||
_MlirGvLast = la
|
||
_MlirGvPrev = pr
|
||
_MlirGvSbRead1 = r1
|
||
_MlirGvSbRead2 = r2
|
||
_MlirGvSbName = nm
|
||
_MlirGvSbVal = vl
|
||
_MlirGvTyBuf = tyb
|
||
_MlirGvNameBuf = nb
|
||
_MlirGvPathStack = ps
|
||
return 1
|
||
|
||
|
||
def _MlirGlobalInitNeedsRegion(x: str) -> int:
|
||
"""Return 1 if a global initializer must go through the initializer region, else 0.
|
||
|
||
The attribute path (tested; do not regress) covers only c strings, string literals, scalar
|
||
literals and pure-scalar arrays. Everything else uses region: root-level bare `null`/
|
||
`zeroinitializer`, struct `{…}`, arrays with symbol refs/structs/MLIR type prefixes, top-level
|
||
bare `@name`, and pointer/aggregate-prefixed initializers (e.g. `i8* null`, `i8* bitcast (…)`).
|
||
"""
|
||
if x is None or x[0] == '\0':
|
||
return 0
|
||
if x[0] == 'c' and x[1] == '"':
|
||
return 0
|
||
if x[0] == '"':
|
||
return 0
|
||
if x[0] == '[':
|
||
return _MlirGvArrayNeedsRegion(x)
|
||
if x[0] == '{':
|
||
return 1
|
||
if string.strcmp(x, "null") == 0 or string.strcmp(x, "zeroinitializer") == 0:
|
||
# Root-level bare zero uses region: the attribute form `(zeroinitializer)` is rejected by
|
||
# mlir-opt 22.1.8; region emits `llvm.mlir.zero : <top type>` + a direct `llvm.return`.
|
||
return 1
|
||
if x[0] == '@':
|
||
return 1
|
||
c0: int = x[0]
|
||
if c0 == '-' or (c0 >= '0' and c0 <= '9'):
|
||
return 0
|
||
return 1
|
||
|
||
|
||
def _MlirGvArrayNeedsRegion(x: str) -> int:
|
||
"""Return 1 if an array initializer must use the initializer region, else 0.
|
||
|
||
Returns 1 on any of: symbol reference '@' (attribute path cannot express addresses), struct
|
||
'{' (aggregate element), or MLIR type prefix '!' (e.g. a nested sub-array prefix). The
|
||
attribute path (_MlirArrayDenseAt) only handles dense arrays of `value : scalar type`;
|
||
inner type prefixes and sub `zeroinitializer` need the region's insertvalue chain. String
|
||
literals are skipped so '{'/'!' inside them are not misdetected.
|
||
"""
|
||
p: str = x
|
||
while p[0] != '\0':
|
||
ch: int = p[0]
|
||
if ch == '"':
|
||
p = p + 1
|
||
while p[0] != '\0' and p[0] != '"':
|
||
# LLVM strings use \xx hex escapes; skip the byte after a '\'
|
||
if p[0] == 0x5C and p[1] != '\0':
|
||
p = p + 1
|
||
p = p + 1
|
||
if p[0] == '"':
|
||
p = p + 1
|
||
continue
|
||
if ch == '@' or ch == '{' or ch == '!':
|
||
return 1
|
||
p = p + 1
|
||
return 0
|
||
|
||
|
||
def _MlirGlobalInitRegion(buf: str, size: sizet,
|
||
pool: memhub.MemBuddy*, gv: GlobalVariable*):
|
||
"""Render the initializer region body (lines indented 2 spaces, without the outer braces).
|
||
|
||
Each line ends with '\\n'; the caller appends '}' and the module layer re-indents by 2.
|
||
"""
|
||
global _MlirGvPool, _MlirGvLeafSeq, _MlirGvInsSeq
|
||
x: str = gv.Initializer
|
||
if x is None:
|
||
return
|
||
# Reused buffers (lazily allocated once, shared across globals) — allocating per global without
|
||
# freeing exhausted the module sub-allocator for struct/vtable-heavy modules.
|
||
_MlirGvEnsureScratch(pool)
|
||
topbuf: str = _MlirGvTopType
|
||
leafbuf: str = _MlirGvLeafBuf
|
||
insbuf: str = _MlirGvInsBuf
|
||
lastbuf: str = _MlirGvLast
|
||
prevbuf: str = _MlirGvPrev
|
||
topbuf[0] = '\0'
|
||
leafbuf[0] = '\0'
|
||
insbuf[0] = '\0'
|
||
lastbuf[0] = '\0'
|
||
prevbuf[0] = '\0'
|
||
TypePrint(topbuf, 4096, gv.Ty, pool)
|
||
|
||
_MlirGvPool = pool
|
||
_MlirGvLeafSeq = 0
|
||
_MlirGvInsSeq = 0
|
||
|
||
is_agg: int = 0
|
||
match gv.Ty:
|
||
case LLVMType.Struct(sf, sfc, sn):
|
||
is_agg = 1
|
||
case LLVMType.Array(ae, ac):
|
||
is_agg = 1
|
||
case _:
|
||
is_agg = 0
|
||
if is_agg != 0:
|
||
# Start of the insertvalue chain: top-level undef
|
||
string.strcpy(prevbuf, "%undef")
|
||
|
||
_MlirGvWalk(x, gv.Ty, "", 0)
|
||
|
||
# Emit leaf ops first, then the undef + insertvalue chain
|
||
_append_cstr(buf, size, leafbuf)
|
||
if is_agg != 0:
|
||
_append_cstr(buf, size, " %undef = llvm.mlir.undef : ")
|
||
_append_cstr(buf, size, topbuf)
|
||
_append_cstr(buf, size, "\n")
|
||
_append_cstr(buf, size, insbuf)
|
||
_append_cstr(buf, size, " llvm.return ")
|
||
if lastbuf[0] != '\0':
|
||
_append_cstr(buf, size, lastbuf)
|
||
else:
|
||
_append_cstr(buf, size, "%undef")
|
||
_append_cstr(buf, size, " : ")
|
||
_append_cstr(buf, size, topbuf)
|
||
_append_cstr(buf, size, "\n")
|
||
|
||
|
||
def _MlirGvWalk(p: str, ty: LLVMType*, path: str, depth: int) -> str:
|
||
"""Walk one node of the initializer-tree × type-tree in parallel; returns the new cursor.
|
||
|
||
The result SSA name goes to _MlirGvLast (leaf = materialized name; aggregate = last
|
||
insertvalue name; empty aggregate = %undef). path looks like "" / "0" / "0, 1".
|
||
"""
|
||
if depth > 64:
|
||
MlirFail("MLIR render: global initializer nested too deeply (> 64 levels)")
|
||
if ty is None:
|
||
MlirFail("MLIR render: global initializer type tree missing (cannot determine the element MLIR type)")
|
||
p = _mlir_ws(p)
|
||
if p[0] == '\0':
|
||
MlirFail("MLIR render: global initializer text ended unexpectedly")
|
||
# Explicit array type prefix (e.g. "[3 x i8] [i8 1, i8 2]") is read off first
|
||
if p[0] == '[' and _MlirGvIsArrayType(p) != 0:
|
||
tb: str = _MlirGvSbRead1
|
||
p = _mlir_read_type(p, tb, 512)
|
||
p = _mlir_ws(p)
|
||
# Explicit struct type prefix (e.g. "{ i32, void ()*, i8* } { i32 65535, … }" for a
|
||
# llvm.global_ctors element) is read off first; `{…}` may be a type or a literal, told apart
|
||
# by whether another `{` immediately follows (see _MlirGvIsStructType).
|
||
if p[0] == '{' and _MlirGvIsStructType(p) != 0:
|
||
stb: str = _MlirGvSbRead1
|
||
p = _mlir_read_type(p, stb, 512)
|
||
p = _mlir_ws(p)
|
||
# An MLIR-form aggregate type prefix (`!llvm.array<…>` / `!llvm.struct<…>`) immediately
|
||
# followed by an aggregate literal is read off first: it comes from upper layers via
|
||
# mlirlite.TypePrint (e.g. a nested sub-array prefix `!llvm.array<16 x i8>[i8 0, …]`); the
|
||
# LLVM-form predicates above do not recognize a '!' prefix.
|
||
if p[0] == '!':
|
||
mtb: str = _MlirGvSbRead1
|
||
qm: str = _mlir_read_type(p, mtb, 512)
|
||
qm = _mlir_ws(qm)
|
||
if qm[0] == '[' or qm[0] == '{':
|
||
p = qm
|
||
if p[0] == '{':
|
||
return _MlirGvWalkStruct(p, ty, path, depth)
|
||
if p[0] == '[':
|
||
return _MlirGvWalkArray(p, ty, path, depth)
|
||
return _MlirGvWalkLeaf(p, ty, path, depth)
|
||
|
||
|
||
def _MlirGvIsArrayType(p: str) -> int:
|
||
"""Return 1 if the cursor is at an LLVM array type prefix "[N x TY]" (vs. an array literal)."""
|
||
if p is None or p[0] != '[':
|
||
return 0
|
||
q: str = p + 1
|
||
nd: int = 0
|
||
while q[0] >= '0' and q[0] <= '9':
|
||
q = q + 1
|
||
nd += 1
|
||
if nd == 0 or q[0] != ' ':
|
||
return 0
|
||
q = q + 1
|
||
if q[0] != 'x' or q[1] != ' ':
|
||
return 0
|
||
return 1
|
||
|
||
|
||
def _MlirGvIsStructType(p: str) -> int:
|
||
"""Return 1 if the cursor is at an LLVM struct TYPE prefix `{ … } { … }` (vs. a struct literal).
|
||
|
||
`{ … }` serves as both struct-type and struct-literal syntax, so the first brace group alone
|
||
is ambiguous. Reliable test: read the first brace group with depth balancing (skipping quoted
|
||
text, where LLVM strings may contain `{`/`}`); if the next non-space token is still '{', the
|
||
first group was a type prefix and the second is the initializer, else the group is the value.
|
||
"""
|
||
if p is None or p[0] != '{':
|
||
return 0
|
||
q: str = p
|
||
dep: int = 0
|
||
while q[0] != '\0':
|
||
ch: int = q[0]
|
||
if ch == '"':
|
||
q = q + 1
|
||
while q[0] != '\0' and q[0] != '"':
|
||
if q[0] == 0x5C and q[1] != '\0':
|
||
q = q + 1
|
||
q = q + 1
|
||
if q[0] == '"':
|
||
q = q + 1
|
||
continue
|
||
if ch == '{':
|
||
dep += 1
|
||
elif ch == '}':
|
||
dep -= 1
|
||
if dep == 0:
|
||
q = q + 1
|
||
break
|
||
q = q + 1
|
||
if dep != 0:
|
||
return 0
|
||
q = _mlir_ws(q)
|
||
if q[0] == '{':
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def _MlirGvPathChild(path: str, idx: int, depth: int) -> str:
|
||
"""Build a child index path from the parent path and child index (top-level "" → "0").
|
||
|
||
Writes to the depth-slotted reuse stack (slot = depth): ancestors occupy 0..depth-1, this
|
||
level writes slot depth, the child writes depth+1, so nothing overlaps; avoids per-node allocs.
|
||
"""
|
||
if depth < 0 or depth >= GV_PATH_MAX:
|
||
MlirFail("MLIR render: index path depth out of range")
|
||
nb: str = _MlirGvPathStack + depth * GV_PATH_SLOT
|
||
if path[0] == '\0':
|
||
viperlib.snprintf(nb, GV_PATH_SLOT, "%d", idx)
|
||
else:
|
||
viperlib.snprintf(nb, GV_PATH_SLOT, "%s, %d", path, idx)
|
||
return nb
|
||
|
||
|
||
def _MlirGvWalkStruct(p: str, ty: LLVMType*, path: str, depth: int) -> str:
|
||
"""Struct initializer { elem, elem, … }: field types come from the type tree's Struct field chain."""
|
||
fields: ParamNode* = None
|
||
match ty:
|
||
case LLVMType.Struct(sf, sfc, sn):
|
||
fields = sf
|
||
case _:
|
||
MlirFail("MLIR render: initializer is a struct but the global type is not a struct")
|
||
cur: ParamNode* = fields
|
||
p = p + 1
|
||
idx: int = 0
|
||
done: int = 0
|
||
guard: int = 0
|
||
while done == 0:
|
||
guard += 1
|
||
if guard > 4096:
|
||
MlirFail("MLIR render: too many struct initializer elements")
|
||
p = _mlir_ws(p)
|
||
if p[0] == '\0':
|
||
MlirFail("MLIR render: struct initializer missing '}'")
|
||
if p[0] == '}':
|
||
p = p + 1
|
||
done = 1
|
||
elif p[0] == ',':
|
||
p = p + 1
|
||
else:
|
||
fty: LLVMType* = None
|
||
if cur is not None:
|
||
fty = paramnode_get_ty(cur)
|
||
p = _MlirGvWalk(p, fty, _MlirGvPathChild(path, idx, depth), depth + 1)
|
||
if cur is not None:
|
||
cur = paramnode_get_next(cur)
|
||
idx += 1
|
||
if idx == 0:
|
||
string.strcpy(_MlirGvLast, "%undef")
|
||
return p
|
||
|
||
|
||
def _MlirGvWalkArray(p: str, ty: LLVMType*, path: str, depth: int) -> str:
|
||
"""Array initializer [ elem, elem, … ]: element type comes from the type tree's Array element type."""
|
||
elem_ty: LLVMType* = None
|
||
match ty:
|
||
case LLVMType.Array(ae, ac):
|
||
elem_ty = ae
|
||
case _:
|
||
MlirFail("MLIR render: initializer is an array but the global type is not an array")
|
||
p = p + 1
|
||
idx: int = 0
|
||
done: int = 0
|
||
guard: int = 0
|
||
while done == 0:
|
||
guard += 1
|
||
if guard > 65536:
|
||
MlirFail("MLIR render: too many array initializer elements")
|
||
p = _mlir_ws(p)
|
||
if p[0] == '\0':
|
||
MlirFail("MLIR render: array initializer missing ']'")
|
||
if p[0] == ']':
|
||
p = p + 1
|
||
done = 1
|
||
elif p[0] == ',':
|
||
p = p + 1
|
||
else:
|
||
p = _MlirGvWalk(p, elem_ty, _MlirGvPathChild(path, idx, depth), depth + 1)
|
||
idx += 1
|
||
if idx == 0:
|
||
string.strcpy(_MlirGvLast, "%undef")
|
||
return p
|
||
|
||
|
||
def _MlirGvIsBareValue(p: str) -> int:
|
||
"""Return 1 if the cursor is DIRECTLY at a value (no LLVM/MLIR type prefix).
|
||
|
||
TPV root initializers may be bare (e.g. a string-pointer global's
|
||
`getelementptr inbounds ([N x i8], … @.str.x …)`, or an object storage reference `@name`);
|
||
aggregate elements are usually `<type> <value>`. A type token never starts with
|
||
`@`/digit/`-`/`.` nor has the names bitcast/getelementptr/null/zeroinitializer, so this is unambiguous.
|
||
"""
|
||
if p is None or p[0] == '\0':
|
||
return 0
|
||
if p[0] == '@':
|
||
return 1
|
||
if string.strncmp(p, "bitcast", 7) == 0:
|
||
return 1
|
||
if string.strncmp(p, "getelementptr", 13) == 0:
|
||
return 1
|
||
if string.strncmp(p, "null", 4) == 0:
|
||
return 1
|
||
if string.strncmp(p, "zeroinitializer", 15) == 0:
|
||
return 1
|
||
c0: int = p[0]
|
||
if c0 == '-' or (c0 >= '0' and c0 <= '9') or c0 == '.':
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def _MlirGvWalkLeaf(p: str, ty: LLVMType*, path: str, depth: int) -> str:
|
||
"""Leaf element `<LLVM type> <value>`: the value parses to a symbol ref / null / scalar literal.
|
||
|
||
The element MLIR type comes from the type tree (ty), not the text's type token. A top-level
|
||
leaf (depth == 0, e.g. a pointer global) skips the insertvalue chain; only leaves inside an
|
||
aggregate need insertion.
|
||
"""
|
||
p = _mlir_ws(p)
|
||
if _MlirGvIsBareValue(p) == 0:
|
||
# Element carries its own type token (`i8* null` / `!llvm.ptr null`): used only to advance the cursor
|
||
tb: str = _MlirGvSbRead2
|
||
p = _mlir_read_type(p, tb, 512)
|
||
p = _mlir_ws(p)
|
||
if p[0] == '\0':
|
||
MlirFail("MLIR render: leaf initializer has only a type and is missing a value")
|
||
if p[0] == '@':
|
||
# Bare symbol reference (strip LLVM quotes → re-add MLIR-style via _MlirSymRefAppend)
|
||
nb: str = _MlirGvSbName
|
||
p = _MlirGvReadSymName(p, nb, 512)
|
||
_MlirGvEmitAddressOf(nb)
|
||
elif string.strncmp(p, "bitcast", 7) == 0 \
|
||
or string.strncmp(p, "getelementptr", 13) == 0:
|
||
# Treat the whole constexpr as a symbol reference (bitcast is identity under opaque pointers; GEP takes the first element's address)
|
||
nb2: str = _MlirGvSbName
|
||
p = _MlirGvScanConstExprSym(p, nb2, 512)
|
||
_MlirGvEmitAddressOf(nb2)
|
||
elif string.strncmp(p, "null", 4) == 0:
|
||
_MlirGvEmitZero(ty)
|
||
p = p + 4
|
||
elif string.strncmp(p, "zeroinitializer", 15) == 0:
|
||
_MlirGvEmitZero(ty)
|
||
p = p + 15
|
||
else:
|
||
c0: int = p[0]
|
||
if c0 == '-' or (c0 >= '0' and c0 <= '9') or c0 == '.':
|
||
vb: str = _MlirGvSbVal
|
||
p = _mlir_read_value(p, vb, 512)
|
||
_MlirGvEmitConst(vb, ty)
|
||
else:
|
||
# Unsupported leaf form: fail loud with the raw text, never emit wrong output silently
|
||
emb: str = _MlirGvPool.alloc(string.strlen(p) + 128)
|
||
if emb is not None:
|
||
viperlib.snprintf(emb, string.strlen(p) + 128,
|
||
"MLIR render: global initializer leaf form unsupported in v1: '%s'", p)
|
||
MlirFail(emb)
|
||
MlirFail("MLIR render: global initializer leaf form unsupported in v1")
|
||
if depth > 0:
|
||
_MlirGvInsertLeaf(path)
|
||
return p
|
||
|
||
|
||
def _MlirGvScanConstExprSym(p: str, out: str, outsz: sizet) -> str:
|
||
"""Scan `bitcast (…)` / `getelementptr … (…)`: extract the first '@' symbol name inside the
|
||
paren group (quotes stripped, written to out); returns the cursor after the group."""
|
||
q: str = p
|
||
while q[0] != '\0' and q[0] != '(':
|
||
q = q + 1
|
||
if q[0] != '(':
|
||
MlirFail("MLIR render: constant expression missing '(' (bitcast/getelementptr)")
|
||
dep: int = 0
|
||
at: str = None
|
||
guard: int = 0
|
||
while q[0] != '\0':
|
||
guard += 1
|
||
if guard > 4096:
|
||
MlirFail("MLIR render: constant expression too long")
|
||
ch: int = q[0]
|
||
if ch == '(':
|
||
dep += 1
|
||
elif ch == ')':
|
||
dep -= 1
|
||
if dep == 0:
|
||
q = q + 1
|
||
break
|
||
elif ch == '@' and at is None:
|
||
at = q
|
||
q = q + 1
|
||
if at is None:
|
||
MlirFail("MLIR render: symbol reference '@' not found in constant expression")
|
||
_MlirGvReadSymName(at, out, outsz)
|
||
return q
|
||
|
||
|
||
def _MlirGvReadSymName(at: str, out: str, outsz: sizet) -> str:
|
||
"""Read a symbol name at '@', strip LLVM-style quotes ("…"); returns the cursor after the name."""
|
||
if at is None or at[0] != '@':
|
||
MlirFail("MLIR render: expected a symbol reference ('@')")
|
||
q: str = at + 1
|
||
n: sizet = 0
|
||
if q[0] == '"':
|
||
q = q + 1
|
||
while q[0] != '\0' and q[0] != '"':
|
||
if n + 1 < outsz:
|
||
out[n] = q[0]
|
||
n += 1
|
||
q = q + 1
|
||
if q[0] == '"':
|
||
q = q + 1
|
||
else:
|
||
while q[0] != '\0' and _MlirGvSymNameEnd(q[0]) == 0:
|
||
if n + 1 < outsz:
|
||
out[n] = q[0]
|
||
n += 1
|
||
q = q + 1
|
||
out[n] = '\0'
|
||
if n == 0:
|
||
MlirFail("MLIR render: empty symbol name")
|
||
return q
|
||
|
||
|
||
def _MlirGvSymNameEnd(ch: int) -> int:
|
||
"""Terminator for a bare symbol name (whitespace / ',' / ')' / ']' / '}')."""
|
||
if ch == 0 or ch == 32 or ch == 9 or ch == 10 or ch == 13:
|
||
return 1
|
||
if ch == ',' or ch == ')' or ch == ']' or ch == '}':
|
||
return 1
|
||
return 0
|
||
|
||
|
||
def _MlirGvNextLeafName() -> str:
|
||
"""Allocate the next leaf SSA name (%v0, %v1, …; reuses the shared name buffer — use immediately)."""
|
||
global _MlirGvLeafSeq
|
||
ln: str = _MlirGvNameBuf
|
||
viperlib.snprintf(ln, 32, "%%v%d", _MlirGvLeafSeq)
|
||
_MlirGvLeafSeq += 1
|
||
return ln
|
||
|
||
|
||
def _MlirGvEmitAddressOf(name: str):
|
||
"""Emit a leaf op: %vN = llvm.mlir.addressof @"name" : !llvm.ptr."""
|
||
ln: str = _MlirGvNextLeafName()
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " ")
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, ln)
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " = llvm.mlir.addressof ")
|
||
_MlirSymRefAppend(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, name)
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " : !llvm.ptr\n")
|
||
string.strcpy(_MlirGvLast, ln)
|
||
|
||
|
||
def _MlirGvEmitZero(ty: LLVMType*):
|
||
"""Emit a leaf op: %vN = llvm.mlir.zero : <element MLIR type>."""
|
||
mt: str = _MlirGvTyBuf
|
||
mt[0] = '\0'
|
||
TypePrint(mt, 4096, ty, _MlirGvPool)
|
||
ln: str = _MlirGvNextLeafName()
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " ")
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, ln)
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " = llvm.mlir.zero : ")
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, mt)
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, "\n")
|
||
string.strcpy(_MlirGvLast, ln)
|
||
|
||
|
||
def _MlirGvEmitConst(val: str, ty: LLVMType*):
|
||
"""Emit a leaf op: %vN = llvm.mlir.constant(<lit> : <element MLIR type>) : <same type>."""
|
||
mt: str = _MlirGvTyBuf
|
||
mt[0] = '\0'
|
||
TypePrint(mt, 4096, ty, _MlirGvPool)
|
||
if mt[0] != 'i' and mt[0] != 'f':
|
||
MlirFail("MLIR render: global initializer scalar element type is neither integer nor float")
|
||
ln: str = _MlirGvNextLeafName()
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " ")
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, ln)
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " = llvm.mlir.constant(")
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, val)
|
||
# i1's `true`/`false` are MLIR bare boolean keywords and take no type suffix
|
||
# (`llvm.mlir.constant(false : i1)` errors "expected ')'"); other scalars must use
|
||
# `value : type` (otherwise they are untyped attrs and mismatch the result type).
|
||
if string.strcmp(val, "true") != 0 and string.strcmp(val, "false") != 0:
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, " : ")
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, mt)
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, ") : ")
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, mt)
|
||
_append_cstr(_MlirGvLeafBuf, GV_REGION_BUF_SIZE, "\n")
|
||
string.strcpy(_MlirGvLast, ln)
|
||
|
||
|
||
def _MlirGvInsertLeaf(path: str):
|
||
"""Emit an insertvalue (multi-index path; the type suffix is always the top-level aggregate type)."""
|
||
global _MlirGvInsSeq
|
||
_MlirGvInsSeq += 1
|
||
sn: str = _MlirGvNameBuf
|
||
viperlib.snprintf(sn, 32, "%%s%d", _MlirGvInsSeq)
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, " ")
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, sn)
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, " = llvm.insertvalue ")
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, _MlirGvLast)
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, ", ")
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, _MlirGvPrev)
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, "[")
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, path)
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, "] : ")
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, _MlirGvTopType)
|
||
_append_cstr(_MlirGvInsBuf, GV_REGION_BUF_SIZE, "\n")
|
||
string.strcpy(_MlirGvPrev, sn)
|
||
string.strcpy(_MlirGvLast, sn)
|
||
|
||
|
||
def _MlirAppendIndented(dst: str, dst_size: sizet, src: str,
|
||
indent: int, pool: memhub.MemBuddy*):
|
||
"""Append each line of src to dst with indent leading spaces (module member indentation)."""
|
||
if dst is None or src is None:
|
||
return
|
||
cb: str = pool.alloc(2)
|
||
if cb is None:
|
||
_append_cstr(dst, dst_size, src)
|
||
return
|
||
p: str = src
|
||
while p[0] != '\0':
|
||
for i in range(indent):
|
||
_append_cstr(dst, dst_size, " ")
|
||
while p[0] != '\0' and p[0] != '\n':
|
||
cb[0] = p[0]
|
||
cb[1] = '\0'
|
||
_append_cstr(dst, dst_size, cb)
|
||
p = p + 1
|
||
if p[0] == '\n':
|
||
_append_cstr(dst, dst_size, "\n")
|
||
p = p + 1
|
||
|
||
|
||
# Internal helpers
|
||
# _append_cstr write-position cache (perf: O(1) positioning for consecutive appends to the same
|
||
# buffer, avoiding O(n²) strlen from the start — the IR-serialization hot spot). Falls back to
|
||
# strlen when the buffer switches or is reset.
|
||
_g_last_dst: uint64 = 0
|
||
_g_last_pos: sizet = 0
|
||
|
||
def _append_cstr(dst: str, dst_size: sizet, src: str):
|
||
"""Append C string src to the end of dst; set the truncation flag on buffer-limit truncation."""
|
||
global _g_last_dst, _g_last_pos, _MlirModOutTrunc
|
||
if dst is None or src is None: return
|
||
dlen: sizet = 0
|
||
if _g_last_dst == uint64(dst):
|
||
dlen = _g_last_pos
|
||
# Buffer was reset (caller reused the same address after buf[0]=0) → back to the head
|
||
if dlen > 0 and dst[0] == '\0':
|
||
dlen = 0
|
||
# Cache-consistency check: the cached end must still be the text end (trailing NUL, previous
|
||
# byte non-NUL). Interleaved writers or a reused pool address make the cached pos stale, so
|
||
# appends land past the first NUL and become invisible; on failure fall back to strlen.
|
||
if dlen == 0:
|
||
if dst[0] != '\0':
|
||
dlen = string.strlen(dst)
|
||
elif dst[dlen] != '\0' or dst[dlen - 1] == '\0':
|
||
dlen = string.strlen(dst)
|
||
else:
|
||
dlen = string.strlen(dst)
|
||
_g_last_dst = uint64(dst)
|
||
# Unsigned underflow guard: if dlen exceeds the buffer size (corrupted/dangling buffer)
|
||
# return immediately, else remain = dst_size - dlen underflows and writes out of bounds.
|
||
if dlen >= dst_size:
|
||
_MlirModOutTrunc = 1
|
||
return
|
||
slen: sizet = string.strlen(src)
|
||
remain: sizet = dst_size - dlen
|
||
if remain <= 0:
|
||
_MlirModOutTrunc = 1
|
||
return
|
||
i: sizet = 0
|
||
while i < slen and i + 1 < remain:
|
||
dst[dlen + i] = src[i]
|
||
i += 1
|
||
if i < slen:
|
||
_MlirModOutTrunc = 1
|
||
dst[dlen + i] = '\0'
|
||
_g_last_pos = dlen + i |