Compare commits

...
4 Commits
30 changed files with 776 additions and 1316 deletions

No files matched your search

+2 -7
View File
@@ -440,8 +440,6 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix:
nm[tl] = 0
is_last: int = 0
if idx + 1 >= token_count: is_last = 1
builtin: int = 0
if string.strcmp(nm, "t") == 0 or string.strcmp(nm, "c") == 0: builtin = 1
dep_sha1: str = _PhaseAResolveDepSha1(nm)
txt_ok: int = 0
if dep_sha1 is not None: txt_ok = _PhaseATextLLExists(temp_dir, td_len, dep_sha1)
@@ -449,10 +447,7 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix:
# stack pointer for a literal -> printf %s crashes and the graph output is swallowed)
br_1: str = "|-- "
br_2: str = "`-- "
if builtin != 0:
if is_last != 0: stdio.printf("%s%s%s (builtin)\n", prefix, br_2, nm)
else: stdio.printf("%s%s%s (builtin)\n", prefix, br_1, nm)
elif dep_sha1 is None:
if dep_sha1 is None:
if is_last != 0: stdio.printf("%s%s%s (unresolved)\n", prefix, br_2, nm)
else: stdio.printf("%s%s%s (unresolved)\n", prefix, br_1, nm)
elif txt_ok != 0:
@@ -463,7 +458,7 @@ def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str, depth: int, prefix:
else: stdio.printf("%s%s%s [MISS] text.ll=%s\n", prefix, br_1, nm, dep_sha1)
# Drill down: MISS follows the whole chain (to PA_GRAPH_DEPTH_MAX); OK probes 1 level only
can_expand: int = 0
if dep_sha1 is not None and builtin == 0:
if dep_sha1 is not None:
if txt_ok == 0:
if depth + 1 < PA_GRAPH_DEPTH_MAX: can_expand = 1
else:
+19 -28
View File
@@ -212,7 +212,20 @@ def _PyiTypeStr(buf: bytes, buf_size: sizet, pos: sizet, annot: ast.AST*, depth:
if at.attr is not None: pos = _PyiEmit(buf, buf_size, pos, at.attr)
return pos
# Subscript: subscripted type (e.g. list[int], t.CArray[elem, count])
# Call: attribute marker with arguments (e.g. attr.aligned(16), attr.section(".x"))
if k == ast.ASTKind.Call:
cl_t: ast.Call* = ast.Call*(annot)
if cl_t.func is None: return pos
pos = _PyiTypeStr(buf, buf_size, pos, cl_t.func, depth + 1)
pos = _PyiEmit(buf, buf_size, pos, "(")
if cl_t.args is not None:
for ai_t in range(int(len(cl_t.args))):
if ai_t > 0: pos = _PyiEmit(buf, buf_size, pos, ", ")
pos = _PyiTypeStr(buf, buf_size, pos, cl_t.args.get(ai_t), depth + 1)
pos = _PyiEmit(buf, buf_size, pos, ")")
return pos
# Subscript: subscripted type (e.g. list[int], CArray[elem, count])
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(annot)
pos = _PyiTypeStr(buf, buf_size, pos, sub.value, depth + 1)
@@ -776,27 +789,8 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
tree_children: list[ast.AST*]* = tree.children
n: sizet = len(tree_children)
# Pass 1: detect import t/c
has_import_t: int = 0
has_import_c: int = 0
for i in range(int(n)):
stmt: ast.AST* = tree_children.get(i)
if stmt is not None and stmt.kind() == ast.ASTKind.Import:
imp: ast.Import* = ast.Import*(stmt)
if imp.names is not None:
imp_names: list[ast.AST*]* = imp.names
an: sizet = len(imp_names)
for ai in range(int(an)):
al: ast.Alias* = ast.Alias*(imp_names.get(ai))
if al is not None and al.name is not None:
if string.strcmp(al.name, "t") == 0: has_import_t = 1
elif string.strcmp(al.name, "c") == 0: has_import_c = 1
# Add missing import t/c
if has_import_t == 0 and has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import t, c\n\n")
elif has_import_t == 0: pos = _PyiEmit(buf, buf_size, pos, "import t\n\n")
elif has_import_c == 0: pos = _PyiEmit(buf, buf_size, pos, "import c\n\n")
else: pos = _PyiEmit(buf, buf_size, pos, "\n")
# t / c are retired compiler-internal modules: the stub never injects their imports.
pos = _PyiEmit(buf, buf_size, pos, "\n")
# Pass 2: emit declarations
for i in range(int(n)):
@@ -886,12 +880,9 @@ def _GeneratePyiFromAst(mb: memhub.MemBuddy*, tree: ast.AST*, rel_path: str, buf
# If there is a value (e.g. a CDefine constant) emit = value.
# CTypedef is special: its value is a type expression, rendered by _PyiTypeStr.
is_ctypedef_ann: int = 0
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Attribute:
ctd_at: ast.Attribute* = ast.Attribute*(aa.annotation)
if ctd_at.attr is not None and string.strcmp(ctd_at.attr, "CTypedef") == 0:
if ctd_at.value is not None and ctd_at.value.kind() == ast.ASTKind.Name:
ctd_mod: ast.Name* = ast.Name*(ctd_at.value)
if ctd_mod.id is not None and string.strcmp(ctd_mod.id, "t") == 0: is_ctypedef_ann = 1
if aa.annotation is not None and aa.annotation.kind() == ast.ASTKind.Name:
ctd_nm: ast.Name* = ast.Name*(aa.annotation)
if ctd_nm.id is not None and string.strcmp(ctd_nm.id, "CTypedef") == 0: is_ctypedef_ann = 1
if is_ctypedef_ann != 0 and aa.value is not None:
pos = _PyiEmit(buf, buf_size, pos, " = ")
pos = _PyiTypeStr(buf, buf_size, pos, aa.value)
+63 -26
View File
@@ -1631,6 +1631,49 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
# vpi caches `-> None` verbatim as "None"; map None to Void so cross-module
# `-> None` methods do not fail fast with an unresolvable return type.
if string.strcmp(seg, "None") == 0: return mlirlite.Void(pool)
# Descriptor / storage / value-reference marker wrappers: CConst[T], State[T], CExport[T], ...
# The marker carries no value type, so resolve the wrapped type.
if seg_len_ps > 0 and seg[seg_len_ps - 1] == ']':
lb_mk: str = string.strchr(seg, '[')
if lb_mk is not None:
lb_off: sizet = sizet(uint64(lb_mk) - uint64(seg))
if lb_off > 0 and seg_len_ps > lb_off + 1:
mk_len: sizet = lb_off
mk_buf: bytes = stdlib.malloc(mk_len + 1)
if mk_buf is not None:
string.strncpy(mk_buf, seg, mk_len)
mk_buf[mk_len] = '\0'
mk_name: str = mk_buf
# `attr.<name>` / `attr.<name>(...)` markers are builtin descriptors.
is_attr_marker: int = 0
if string.strncmp(mk_name, "attr.", 5) == 0 or string.strcmp(mk_name, "attr") == 0: is_attr_marker = 1
dot_mk: str = string.strrchr(mk_name, '.')
if dot_mk is not None: mk_name = dot_mk + 1
is_marker: int = is_attr_marker
if string.strcmp(mk_name, "CConst") == 0 or string.strcmp(mk_name, "CVolatile") == 0 \
or string.strcmp(mk_name, "BigEndian") == 0 or string.strcmp(mk_name, "LittleEndian") == 0 \
or string.strcmp(mk_name, "CStatic") == 0 or string.strcmp(mk_name, "CExport") == 0 \
or string.strcmp(mk_name, "CExtern") == 0 or string.strcmp(mk_name, "State") == 0 \
or string.strcmp(mk_name, "CDefine") == 0 or string.strcmp(mk_name, "CRef") == 0 \
or string.strcmp(mk_name, "CTempRef") == 0 or string.strcmp(mk_name, "CAutoPtr") == 0 \
or string.strcmp(mk_name, "attr") == 0:
is_marker = 1
if is_marker != 0:
inner_len_mk: sizet = seg_len_ps - lb_off - 2
inner_mk: bytes = stdlib.malloc(inner_len_mk + 1)
if inner_mk is not None:
string.strncpy(inner_mk, seg + lb_off + 1, inner_len_mk)
inner_mk[inner_len_mk] = '\0'
inner_ty_mk: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_mk)
stdlib.free(inner_mk)
# Value-reference parameters use the pointer ABI: &T / &temp T -> T*.
if inner_ty_mk is not None and (string.strcmp(mk_name, "CRef") == 0 or string.strcmp(mk_name, "CTempRef") == 0):
inner_ty_mk = mlirlite.Ptr(pool, inner_ty_mk)
elif string.strcmp(mk_name, "CAutoPtr") == 0:
inner_ty_mk = i8_ptr_ty
stdlib.free(mk_buf)
return inner_ty_mk
stdlib.free(mk_buf)
# rawptr[T] built-in raw pointer (equivalent to t.CPtr[T] -> Ptr(T)); vpi keeps the
# Subscript form verbatim. Same as TypeGenResolve's rawptr branch: Ptr(resolve(T)).
if string.strncmp(seg, "rawptr[", 7) == 0:
@@ -1645,10 +1688,10 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
stdlib.free(inner_buf_rp)
if inner_ty_rp is not None: return mlirlite.Ptr(pool, inner_ty_rp)
return None
# t.CPtr[T] / c.CPtr[T] subscript pointers (nesting allowed, e.g. t.CPtr[t.CPtr]
# -> Ptr(Ptr(i8))); same as TypeGenResolve's Subscript(CPtr) branch: Ptr(resolve(T)).
# CPtr[T] subscript pointers (nesting allowed, e.g. CPtr[CPtr] -> Ptr(Ptr(i8)));
# same as TypeGenResolve's Subscript(CPtr) branch: Ptr(resolve(T)).
cptr_sub_pfx: sizet = 0
if string.strncmp(seg, "t.CPtr[", 7) == 0 or string.strncmp(seg, "c.CPtr[", 7) == 0: cptr_sub_pfx = 7
if string.strncmp(seg, "CPtr[", 6) == 0: cptr_sub_pfx = 6
if cptr_sub_pfx > 0:
seg_len_cp: sizet = string.strlen(seg)
if seg_len_cp > cptr_sub_pfx + 1 and seg[seg_len_cp - 1] == ']':
@@ -1661,15 +1704,12 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
stdlib.free(inner_buf_cp)
if inner_ty_cp is not None: return mlirlite.Ptr(pool, inner_ty_cp)
return None
# t.XXX / c.XXX built-in type mapping
if string.strncmp(seg, "t.", 2) == 0 or string.strncmp(seg, "c.", 2) == 0:
attr_nm: str = seg + 2
# t.CExport is an export marker, not a real type; FuncGenDef maps it to Void,
# Bare builtin type names (int8 / uint64 / sizet / CPtr / CExport ...)
if string.strchr(seg, '.') is None:
# CExport is an export marker, not a real type; FuncGenDef maps it to Void,
# so normalize it here too or cross-module return types fail fast.
if string.strcmp(attr_nm, "CExport") == 0: return mlirlite.Void(pool)
# [stage 2] unified: CTypedef dynamic table first (library typedefs analyzed normally),
# on miss fall back to map_t_type (t/c library internal special-casing).
mapped_ty: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, attr_nm, None, None)
if string.strcmp(seg, "CExport") == 0: return mlirlite.Void(pool)
mapped_ty: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, seg, None, None)
if mapped_ty is not None: return mapped_ty
return None
# module-qualified class name "memhub.MemBuddy" -> use last segment to look up struct
@@ -1689,7 +1729,7 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
# bare enum name -> EnumTable fallback (as above: enums are not in StructTable)
bare_enum_ty: mlirlite.LLVMType* = GenApi.get_enum_base_type(seg)
if bare_enum_ty is not None: return bare_enum_ty
# bare typedef name (e.g. VOIDPTR): CTypedef dynamic table first, on miss fall back to map_t_type
# bare typedef name (e.g. VOIDPTR): CTypedef dynamic table first, on miss fall back to map_builtin_type
mapped_bare: mlirlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, seg, None, None)
if mapped_bare is not None: return mapped_bare
return None
@@ -1700,6 +1740,8 @@ def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*, seg: str) -> mlirlite.L
def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: mlirlite.LLVMType*, i8_ptr_ty: mlirlite.LLVMType*) -> mlirlite.LLVMType*:
"""Resolve a vpi return-annotation string directly to LLVMType without building AST nodes."""
if ret_annot is None or pool is None: return None
# `T1 | T2` unions were retired; a stub containing one must be regenerated.
if string.strchr(ret_annot, '|') is not None: return None
# tuple[T1, T2, ...] -> anonymous struct {T1, T2, ...} (multi-return packing),
# matching TypeGenMixin._resolve_generic_subscript_ty's tuple branch.
@@ -1793,7 +1835,7 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
seg_buf[seg_len2] = '\0'
# CPtr subscript segments must be recognized first: the definition side treats
# `X*[T]` as "CPtr side contributes N pointer levels, the other side the base".
if string.strncmp(seg_buf, "t.CPtr[", 7) == 0 or string.strncmp(seg_buf, "c.CPtr[", 7) == 0:
if string.strncmp(seg_buf, "CPtr[", 6) == 0:
seg_ty_cs: mlirlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf)
if seg_ty_cs is not None:
# Count outer Ptr nesting (same as the definition side). Do not update the
@@ -1813,10 +1855,10 @@ def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*, ret_annot: str, i32_ty: m
if lvl_cs > cptr_sub_lvl:
cptr_sub_lvl = lvl_cs
cptr_sub_ty = seg_ty_cs
elif string.strcmp(seg_buf, "t.CPtr") == 0 or string.strcmp(seg_buf, "CPtr") == 0: has_cptr = 1
elif string.strcmp(seg_buf, "t.CRef") == 0 or string.strcmp(seg_buf, "CRef") == 0: has_cref = 1
elif string.strcmp(seg_buf, "t.CTempRef") == 0 or string.strcmp(seg_buf, "CTempRef") == 0: has_cref = 1
elif string.strcmp(seg_buf, "void") == 0 or string.strcmp(seg_buf, "CVoid") == 0 or string.strcmp(seg_buf, "void") == 0:
elif string.strcmp(seg_buf, "CPtr") == 0: has_cptr = 1
elif string.strcmp(seg_buf, "CRef") == 0: has_cref = 1
elif string.strcmp(seg_buf, "CTempRef") == 0: has_cref = 1
elif string.strcmp(seg_buf, "void") == 0 or string.strcmp(seg_buf, "CVoid") == 0:
# Bare void is an alias of t.CVoid and must be handled the same, else the
# else branch sets first_td and produces the illegal Ptr(Void) (void* = i8*).
has_cvoid = 1
@@ -2614,9 +2656,9 @@ def _vpi_class_has_packed(decorator_list: list[ast.AST*]*) -> int:
if deco is None: continue
if deco.kind() == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(deco)
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: continue
at0: ast.Attribute* = ast.Attribute*(cl.func)
if at0.attr is None or string.strcmp(at0.attr, "Attribute") != 0: continue
if cl.func is None or cl.func.kind() != ast.ASTKind.Name: continue
at0: ast.Name* = ast.Name*(cl.func)
if at0.id is None or string.strcmp(at0.id, "Attribute") != 0: continue
if cl.args is None: continue
an: sizet = len(cl.args)
for ai in range(int(an)):
@@ -3527,12 +3569,7 @@ def _repl_stub_deco_flags(deco_list: list[ast.AST*]*) -> int:
if deco is None: continue
nm: str = None
k: int = deco.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(deco)
if at.attr is not None and at.value is not None and at.value.kind() == ast.ASTKind.Name:
vn: ast.Name* = ast.Name*(at.value)
if vn.id is not None and string.strcmp(vn.id, "t") == 0: nm = at.attr
elif k == ast.ASTKind.Name:
if k == ast.ASTKind.Name:
nn: ast.Name* = ast.Name*(deco)
nm = nn.id
if nm is None: continue
+163 -124
View File
@@ -23,19 +23,20 @@ novirtual class AssignGenMixin:
RecoverFound: int
# Check whether an annotation is t.CDefine (module-level function)
# Check whether an annotation is the CDefine marker (module-level function)
def is_cdefine_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is t.CDefine, else 0."""
"""Return 1 if the annotation is the CDefine marker (bare or CDefine[T]), else 0."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, "CDefine") == 0: return 1
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self.is_cdefine_annotation(bop.left) != 0: return 1
if self.is_cdefine_annotation(bop.right) != 0: return 1
if k == ast.ASTKind.Subscript:
sub_cd: ast.Subscript* = ast.Subscript*(annot)
if sub_cd.value is not None and sub_cd.value.kind() == ast.ASTKind.Name:
mk_cd: ast.Name* = ast.Name*(sub_cd.value)
if mk_cd.id is not None and string.strcmp(mk_cd.id, "CDefine") == 0: return 1
return 0
# extract_cdefine_int_value - extract an integer constant from AnnAssign.value
@@ -325,26 +326,28 @@ novirtual class AssignGenMixin:
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] shift count out of range: the count {v} is >= the left operand width {lhs_bits} bits; " f"the result would be 0 or undefined (not a value the compiler may guess). " f"Fix: use a count < {lhs_bits}, or widen the left operand.", "ValueError")
def _is_cdefine_marker_node(self, node: ast.AST*) -> int:
"""Return 1 if the node is a t.CDefine marker node."""
"""Return 1 if the node is the CDefine marker node (bare or CDefine[T])."""
if node is None: return 0
k: int = node.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None and string.strcmp(at.attr, "CDefine") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None and string.strcmp(nm.id, "CDefine") == 0: return 1
if k == ast.ASTKind.Subscript:
sub_cm: ast.Subscript* = ast.Subscript*(node)
if sub_cm.value is not None and sub_cm.value.kind() == ast.ASTKind.Name:
mk_cm: ast.Name* = ast.Name*(sub_cm.value)
if mk_cm.id is not None and string.strcmp(mk_cm.id, "CDefine") == 0: return 1
return 0
def _define_explicit_annot(self, annot: ast.AST*) -> ast.AST*:
"""Return the explicit type T of a `T | t.CDefine` annotation, or None."""
"""Return the explicit type T of a `CDefine[T]` annotation, or None."""
if annot is None: return None
if annot.kind() != ast.ASTKind.BinOp: return None
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.left is None: return None
if self._is_cdefine_marker_node(bop.left) != 0: return None
return bop.left
if annot.kind() != ast.ASTKind.Subscript: return None
sub_d: ast.Subscript* = ast.Subscript*(annot)
if sub_d.value is None or sub_d.value.kind() != ast.ASTKind.Name: return None
mk_d: ast.Name* = ast.Name*(sub_d.value)
if mk_d.id is None or string.strcmp(mk_d.id, "CDefine") != 0: return None
return sub_d.slice
def _annot_int_bits(self, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Return the bit width of an integer annotation, or 0 if unknown."""
@@ -357,13 +360,17 @@ novirtual class AssignGenMixin:
elif k == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(annot)
nm = n2.id
elif k == ast.ASTKind.Subscript:
# Descriptor wrapper (CConst[T] etc.): the width comes from the wrapped type.
sub_ib: ast.Subscript* = ast.Subscript*(annot)
return self._annot_int_bits(pool, sub_ib.slice)
if nm is None: return 0
return self._int_bits_of_type_name(pool, nm, 0)
def _int_bits_of_type_name(self, pool: memhub.MemBuddy*, nm: str, depth: int) -> int:
"""类型名 → 整数位宽(0=未知/非整数)。depth 防 typedef 环。
覆盖:内建名(int/bool/int8../uint64/sizet)、`t.CXxx`(map_t_type)、
覆盖:内建名(int/bool/int8../uint64/sizet)、`t.CXxx`(map_builtin_type)、
以及**用户 typedef**(如 stdint 的 `WORD: t.CTypedef = uint16` —— 经 lookup_ctypedef
取其底层类型文本再递归定宽)。此前用户 typedef 一律返回 0,导致
`define X: WORD = ...` 的声明宽度不参与校验/物化。
@@ -380,7 +387,7 @@ novirtual class AssignGenMixin:
if string.strcmp(nm, "int32") == 0 or string.strcmp(nm, "uint32") == 0: return 32
if string.strcmp(nm, "int64") == 0 or string.strcmp(nm, "uint64") == 0: return 64
if string.strcmp(nm, "sizet") == 0 or string.strcmp(nm, "size_t") == 0: return 64
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_t_type(pool, nm)
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_builtin_type(pool, nm)
if ty is not None:
match ty:
case mlirlite.LLVMType.Int(bits):
@@ -426,13 +433,16 @@ novirtual class AssignGenMixin:
elif k == ast.ASTKind.Name:
n2: ast.Name* = ast.Name*(annot)
nm = n2.id
elif k == ast.ASTKind.Subscript:
sub_fk: ast.Subscript* = ast.Subscript*(annot)
return self._annot_float_kind(pool, sub_fk.slice)
if nm is None: return 0
return self._float_kind_of_type_name(pool, nm, 0)
def _float_kind_of_type_name(self, pool: memhub.MemBuddy*, nm: str, depth: int) -> int:
"""类型名 → 1 表示它是 32 位 float(f32),0 表示 double 或非浮点/未知。depth 防 typedef 环。
覆盖:内建名 float、`t.CFloat` / `t.CFloat32T`(map_t_type)、用户/include typedef(递归展开)。
覆盖:内建名 float、`t.CFloat` / `t.CFloat32T`(map_builtin_type)、用户/include typedef(递归展开)。
"""
if nm is None or depth > 10: return 0
# 去掉模块/类限定前缀:`t.CFloat` → `CFloat`;`stdint.float32` → `float32`
@@ -441,7 +451,7 @@ novirtual class AssignGenMixin:
return self._float_kind_of_type_name(pool, dot + 1, depth + 1)
if string.strcmp(nm, "float") == 0: return 1
if string.strcmp(nm, "float32") == 0: return 1
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_t_type(pool, nm)
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_builtin_type(pool, nm)
if ty is not None:
match ty:
case mlirlite.LLVMType.Float(bits):
@@ -914,28 +924,20 @@ novirtual class AssignGenMixin:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, name) == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_contains_name(bop.left, name) != 0: return 1
if self._annotation_contains_name(bop.right, name) != 0: return 1
if k == ast.ASTKind.Subscript:
sub_cn: ast.Subscript* = ast.Subscript*(annot)
if sub_cn.slice is not None and self._annotation_contains_name(sub_cn.slice, name) != 0: return 1
return 0
def _annotation_has_cptr_marker(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation union carries a CPtr marker (the star form `<base> | rawptr`)."""
"""Return 1 if the annotation is a CPtr pointer wrapper (the `T*` form)."""
if annot is None: return 0
k: int = annot.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(annot)
if at.attr is not None and string.strcmp(at.attr, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(annot)
if nm.id is not None and string.strcmp(nm.id, "CPtr") == 0: return 1
return 0
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(annot)
if self._annotation_has_cptr_marker(bop.left) != 0: return 1
if self._annotation_has_cptr_marker(bop.right) != 0: return 1
if annot.kind() != ast.ASTKind.Subscript: return 0
sub_cp: ast.Subscript* = ast.Subscript*(annot)
if sub_cp.value is not None and sub_cp.value.kind() == ast.ASTKind.Name:
nm_cp: ast.Name* = ast.Name*(sub_cp.value)
if nm_cp.id is not None and (string.strcmp(nm_cp.id, "CPtr") == 0 or string.strcmp(nm_cp.id, "rawptr") == 0): return 1
if sub_cp.slice is not None: return self._annotation_has_cptr_marker(sub_cp.slice)
return 0
# _is_ptr_element_annotation - check whether an annotation is bytes* or str*
@@ -943,34 +945,36 @@ novirtual class AssignGenMixin:
def _is_ptr_element_annotation(self, annot: ast.AST*) -> int:
"""Return 1 if the annotation is a bytes* or str* form."""
if annot is None: return 0
k: int = annot.kind()
if k != ast.ASTKind.BinOp: return 0
bop: ast.BinOp* = ast.BinOp*(annot)
if bop.op != ast.OpKind.BitOr: return 0
# str/bytes are already i8*; only the union WITH a pointer marker (str*/bytes*) is i8** and
# needs 8-byte element slots. A union that merely carries them (e.g. `const str`, which is
# t.CConst | str and stays i8*) must keep the 1-byte stride.
if self._annotation_has_cptr_marker(annot) != 0:
if self._annotation_contains_name(annot, "str") != 0: return 1
if self._annotation_contains_name(annot, "bytes") != 0: return 1
# Generic type param / nested union | t.CPtr: scalars use their own size, pointers use 8-byte slots
if self._is_generic_tp_or_nested(bop.left) != 0 or self._is_generic_tp_or_nested(bop.right) != 0:
gp_nm: str = None
if bop.left is not None and bop.left.kind() == ast.ASTKind.Name:
gp_left_nm: ast.Name* = ast.Name*(bop.left)
gp_nm = gp_left_nm.id
elif bop.right is not None and bop.right.kind() == ast.ASTKind.Name:
gp_right_nm: ast.Name* = ast.Name*(bop.right)
gp_nm = gp_right_nm.id
if annot.kind() != ast.ASTKind.Subscript: return 0
sub_pe: ast.Subscript* = ast.Subscript*(annot)
if sub_pe.value is None or sub_pe.value.kind() != ast.ASTKind.Name: return 0
pe_nm: ast.Name* = ast.Name*(sub_pe.value)
if pe_nm.id is None: return 0
# Descriptor / storage / ref wrapper: the pointer form lives in the wrapped type.
if string.strcmp(pe_nm.id, "CConst") == 0 or string.strcmp(pe_nm.id, "CVolatile") == 0 \
or string.strcmp(pe_nm.id, "BigEndian") == 0 or string.strcmp(pe_nm.id, "LittleEndian") == 0 \
or string.strcmp(pe_nm.id, "CStatic") == 0 or string.strcmp(pe_nm.id, "CExport") == 0 \
or string.strcmp(pe_nm.id, "CExtern") == 0 or string.strcmp(pe_nm.id, "State") == 0 \
or string.strcmp(pe_nm.id, "CDefine") == 0 or string.strcmp(pe_nm.id, "CRef") == 0 \
or string.strcmp(pe_nm.id, "CTempRef") == 0 or string.strcmp(pe_nm.id, "CAutoPtr") == 0:
return self._is_ptr_element_annotation(sub_pe.slice)
if string.strcmp(pe_nm.id, "CPtr") != 0 and string.strcmp(pe_nm.id, "rawptr") != 0: return 0
inner_pe: ast.AST* = sub_pe.slice
if inner_pe is None: return 0
# str/bytes are already i8*; the pointer layer makes them i8** and needs 8-byte slots.
if self._annotation_contains_name(inner_pe, "str") != 0: return 1
if self._annotation_contains_name(inner_pe, "bytes") != 0: return 1
# Generic type param: a scalar-typed param uses its own size, a pointer/aggregate uses 8 bytes.
if inner_pe.kind() == ast.ASTKind.Name:
inner_nm_pe: ast.Name* = ast.Name*(inner_pe)
gp_nm: str = inner_nm_pe.id
if gp_nm is not None:
# Resolve the generic param via the global gen (direct field access breaks static binding)
gp_resolved: str = self.Gen.TypeGenBase.resolve_generic_type_name(gp_nm)
if gp_resolved is not None and string.strcmp(gp_resolved, gp_nm) != 0:
# Generic param resolved to a scalar => use scalar size, not an 8-byte pointer slot
if self._is_scalar_ty_name(gp_resolved) != 0: return 0
return 1
# 64-bit scalar | CPtr: element is 8 bytes, so use an 8-byte stride (else GEP truncation)
if self._is_64bit_scalar_ty(bop.left) != 0 or self._is_64bit_scalar_ty(bop.right) != 0: return 1
if gp_resolved is not None: return 1
# 64-bit scalar pointee: element is 8 bytes (else GEP truncation).
if self._is_64bit_scalar_ty(inner_pe) != 0: return 1
return 0
def _stg_ty_equal(self, pool: memhub.MemBuddy*, ta: mlirlite.LLVMType*, tb: mlirlite.LLVMType*) -> int:
@@ -1502,34 +1506,22 @@ novirtual class AssignGenMixin:
return 0
def _is_addrlike_call(self, node: ast.AST*) -> int:
"""Return 1 if the rhs is an explicit pointer expression (c.Addr/c.Deref/c.Cast/c.Ptr call, or new-syntax `&x` / `*p`)."""
"""Return 1 if the rhs is an explicit pointer expression (`&x` / `*p`).
"""
if node is None: return 0
# New syntax (R3/R4): `&x` and `*p` parse as UnaryOp(AddrOf) / UnaryOp(Deref) and replace the
# c.Addr / c.Deref call forms. A pointer-valued RHS must not take the module-global
# "object with static storage -> copy the object" branch, so both forms count as addrlike.
# `&x` and `*p` parse as UnaryOp(AddrOf) / UnaryOp(Deref). A pointer-valued RHS must not
# take the module-global "object with static storage -> copy the object" branch.
if node.kind() == ast.ASTKind.UnaryOp:
uo_al: ast.UnaryOp* = ast.UnaryOp*(node)
if uo_al.op == ast.OpKind.AddrOf: return 1
if uo_al.op == ast.OpKind.Deref: return 1
return 0
if node.kind() != ast.ASTKind.Call: return 0
cl: ast.Call* = ast.Call*(node)
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
vn: ast.Name* = ast.Name*(at.value)
if vn.id is None or string.strcmp(vn.id, "c") != 0: return 0
if at.attr is None: return 0
if string.strcmp(at.attr, "Addr") == 0: return 1
if string.strcmp(at.attr, "Deref") == 0: return 1
if string.strcmp(at.attr, "Cast") == 0: return 1
if string.strcmp(at.attr, "Ptr") == 0: return 1
return 0
def _is_addr_of_expr(self, node: ast.AST*) -> int:
"""Return 1 only for an explicit address-of expression (`&x` / `c.Addr(x)`).
"""Return 1 only for an explicit address-of expression (`&x`).
Distinct from _is_addrlike_call, which also counts deref/cast forms: assigning a *deref*
Distinct from _is_addrlike_call, which also counts deref forms: assigning a *deref*
result (`*p`) to a value-typed variable is a legitimate value copy, so only `&x` indicates
a missing `*` in the annotation.
"""
@@ -1538,14 +1530,6 @@ novirtual class AssignGenMixin:
uo_ao: ast.UnaryOp* = ast.UnaryOp*(node)
if uo_ao.op == ast.OpKind.AddrOf: return 1
return 0
if node.kind() != ast.ASTKind.Call: return 0
cl_ao: ast.Call* = ast.Call*(node)
if cl_ao.func is None or cl_ao.func.kind() != ast.ASTKind.Attribute: return 0
at_ao: ast.Attribute* = ast.Attribute*(cl_ao.func)
if at_ao.value is None or at_ao.value.kind() != ast.ASTKind.Name: return 0
vn_ao: ast.Name* = ast.Name*(at_ao.value)
if vn_ao.id is None or string.strcmp(vn_ao.id, "c") != 0: return 0
if at_ao.attr is not None and string.strcmp(at_ao.attr, "Addr") == 0: return 1
return 0
def _DerefPunBits(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> int:
@@ -1577,20 +1561,65 @@ novirtual class AssignGenMixin:
if string.strcmp(ty_nm, "CUInt64T") == 0: return 1
if string.strcmp(ty_nm, "CInt64T") == 0: return 1
if string.strcmp(ty_nm, "CSizeT") == 0: return 1
# builtin 64-bit short names
if string.strcmp(ty_nm, "sizet") == 0: return 1
if string.strcmp(ty_nm, "size_t") == 0: return 1
if string.strcmp(ty_nm, "int64") == 0: return 1
if string.strcmp(ty_nm, "uint64") == 0: return 1
if string.strcmp(ty_nm, "intfast64") == 0: return 1
if string.strcmp(ty_nm, "uintfast64") == 0: return 1
if string.strcmp(ty_nm, "intleast64") == 0: return 1
if string.strcmp(ty_nm, "uintleast64") == 0: return 1
if string.strcmp(ty_nm, "longlong") == 0: return 1
if string.strcmp(ty_nm, "ulonglong") == 0: return 1
if string.strcmp(ty_nm, "ptrdifft") == 0: return 1
return 0
def _is_scalar_ty_name(self, ty_name: str) -> int:
"""Return 1 if the type name is a known pure scalar (whitelist)."""
if ty_name is None: return 0
# Strip the t./c. namespace prefix so int and bare aliases are judged the same
if ty_name[0] == 't' and ty_name[1] == '.': ty_name += 2
elif ty_name[0] == 'c' and ty_name[1] == '.': ty_name += 2
if string.strchr(ty_name, '|') is not None: return 0
if string.strchr(ty_name, '*') is not None: return 0
if string.strcmp(ty_name, "str") == 0: return 0
if string.strcmp(ty_name, "bytes") == 0: return 0
if string.strcmp(ty_name, "CPtr") == 0: return 0
if string.strstr(ty_name, "Ptr") is not None: return 0
# builtin short type names
if string.strcmp(ty_name, "sizet") == 0: return 1
if string.strcmp(ty_name, "size_t") == 0: return 1
if string.strcmp(ty_name, "int8") == 0: return 1
if string.strcmp(ty_name, "int16") == 0: return 1
if string.strcmp(ty_name, "int32") == 0: return 1
if string.strcmp(ty_name, "int64") == 0: return 1
if string.strcmp(ty_name, "uint8") == 0: return 1
if string.strcmp(ty_name, "uint16") == 0: return 1
if string.strcmp(ty_name, "uint32") == 0: return 1
if string.strcmp(ty_name, "uint64") == 0: return 1
if string.strcmp(ty_name, "intfast8") == 0: return 1
if string.strcmp(ty_name, "intfast16") == 0: return 1
if string.strcmp(ty_name, "intfast32") == 0: return 1
if string.strcmp(ty_name, "intfast64") == 0: return 1
if string.strcmp(ty_name, "uintfast8") == 0: return 1
if string.strcmp(ty_name, "uintfast16") == 0: return 1
if string.strcmp(ty_name, "uintfast32") == 0: return 1
if string.strcmp(ty_name, "uintfast64") == 0: return 1
if string.strcmp(ty_name, "intleast8") == 0: return 1
if string.strcmp(ty_name, "intleast16") == 0: return 1
if string.strcmp(ty_name, "intleast32") == 0: return 1
if string.strcmp(ty_name, "intleast64") == 0: return 1
if string.strcmp(ty_name, "uintleast8") == 0: return 1
if string.strcmp(ty_name, "uintleast16") == 0: return 1
if string.strcmp(ty_name, "uintleast32") == 0: return 1
if string.strcmp(ty_name, "uintleast64") == 0: return 1
if string.strcmp(ty_name, "uchar") == 0: return 1
if string.strcmp(ty_name, "short") == 0: return 1
if string.strcmp(ty_name, "ushort") == 0: return 1
if string.strcmp(ty_name, "uint") == 0: return 1
if string.strcmp(ty_name, "long") == 0: return 1
if string.strcmp(ty_name, "ulong") == 0: return 1
if string.strcmp(ty_name, "longlong") == 0: return 1
if string.strcmp(ty_name, "ulonglong") == 0: return 1
if string.strcmp(ty_name, "ptrdifft") == 0: return 1
# Whitelist: known pure scalars (base names + stdint uppercase aliases)
if string.strcmp(ty_name, "int") == 0: return 1
if string.strcmp(ty_name, "char") == 0: return 1
@@ -1913,7 +1942,7 @@ novirtual class AssignGenMixin:
def const_assign_msg(self, kind: str, name: str, src_name: str) -> str:
"""Build the const-assignment error message used by const_assign_fail."""
decl: str = "(annotation contains t.CConst; the source name at the declaration was not recorded)"
if src_name is not None: decl = f"t.CConst | {src_name}"
if src_name is not None: decl = f"CConst | {src_name}"
return GenApi.escape_fstring_copy(f"cannot assign to const variable '{name}' ({kind}); " f"declaration type (including const): {decl}" f"; fix: remove the const descriptor / t.CConst from the declaration, or assign once at the declaration " f"and afterwards use only non-const copies/temporaries; a const object forbids assignment, " f"augmented assignment and element assignment within its scope, and this never degrades to a warning")
def const_assign_fail(self, node: ast.AST*, kind: str, name: str, src_name: str) -> int:
@@ -2242,18 +2271,12 @@ novirtual class AssignGenMixin:
if annot is None or annot.kind() != ast.ASTKind.Subscript: return 0
sub: ast.Subscript* = ast.Subscript*(annot)
if sub.value is None or sub.slice is None: return 0
if sub.value.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(sub.value)
if at.attr is None or string.strcmp(at.attr, "CArray") != 0: return 0
if at.value is None or at.value.kind() != ast.ASTKind.Name: return 0
mv: ast.Name* = ast.Name*(at.value)
if mv.id is None or string.strcmp(mv.id, "t") != 0: return 0
elif sub.value.kind() == ast.ASTKind.Name:
# Bare CArray / builtin rawarray / builtin array are all equivalent to t.CArray
if sub.value.kind() == ast.ASTKind.Name:
# Bare CArray / builtin rawarray / builtin array are all equivalent.
bn: ast.Name* = ast.Name*(sub.value)
if bn.id is None or (string.strcmp(bn.id, "CArray") != 0 and string.strcmp(bn.id, "rawarray") != 0 and string.strcmp(bn.id, "array") != 0): return 0
else: return 0
# Single-param form: slice is not a Tuple (e.g. t.CArray[u64])
# Single-param form: slice is not a Tuple (e.g. CArray[u64])
if sub.slice.kind() != ast.ASTKind.Tuple: return 1
# Two-param form with count=None is equivalent to single-param
tup: ast.Tuple* = ast.Tuple*(sub.slice)
@@ -2405,14 +2428,16 @@ novirtual class AssignGenMixin:
def _prespecialize_generic_annotation(self, trans: HT.Translator*, pool: memhub.MemBuddy*, annot: ast.AST*) -> int:
"""Pre-specialize a generic annotation; return 1 if attempted."""
if annot is None or trans is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
# Take the generic Subscript side: the Subscript itself or the Subscript wrapped by CPtr[T].
ann_ps: ast.AST* = annot
if ann_ps.kind() == ast.ASTKind.Subscript:
sub_wrap_ps: ast.Subscript* = ast.Subscript*(ann_ps)
if sub_wrap_ps.value is not None and sub_wrap_ps.value.kind() == ast.ASTKind.Name:
wk_nm_ps: ast.Name* = ast.Name*(sub_wrap_ps.value)
if wk_nm_ps.id is not None and (string.strcmp(wk_nm_ps.id, "CPtr") == 0 or string.strcmp(wk_nm_ps.id, "rawptr") == 0):
if sub_wrap_ps.slice is not None: ann_ps = sub_wrap_ps.slice
spec_annot: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ps: ast.BinOp* = ast.BinOp*(annot)
if bop_ps.op == ast.OpKind.BitOr:
if bop_ps.left is not None and bop_ps.left.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.left)
elif bop_ps.right is not None and bop_ps.right.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ps.right)
if ann_ps.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(ann_ps)
if spec_annot is None: return 0
sv_ps: ast.AST* = spec_annot.value
if sv_ps is None or sv_ps.kind() != ast.ASTKind.Name: return 0
@@ -2429,14 +2454,16 @@ novirtual class AssignGenMixin:
def _fail_on_degenerated_generic_annotation(self, aa: ast.AnnAssign*, var_name: str, annot: ast.AST*, var_ty: mlirlite.LLVMType*) -> int:
"""Fail fast when a generic annotation degrades to a bare base pointer."""
if aa is None or annot is None or var_ty is None: return 0
# Take the generic Subscript side: the Subscript itself or the Subscript of A*
# Take the generic Subscript side: the Subscript itself or the Subscript wrapped by CPtr[T].
ann_ff: ast.AST* = annot
if ann_ff.kind() == ast.ASTKind.Subscript:
sub_wrap_ff: ast.Subscript* = ast.Subscript*(ann_ff)
if sub_wrap_ff.value is not None and sub_wrap_ff.value.kind() == ast.ASTKind.Name:
wk_nm_ff: ast.Name* = ast.Name*(sub_wrap_ff.value)
if wk_nm_ff.id is not None and (string.strcmp(wk_nm_ff.id, "CPtr") == 0 or string.strcmp(wk_nm_ff.id, "rawptr") == 0):
if sub_wrap_ff.slice is not None: ann_ff = sub_wrap_ff.slice
ann_sub: ast.Subscript* = None
if annot.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(annot)
elif annot.kind() == ast.ASTKind.BinOp:
bop_ff: ast.BinOp* = ast.BinOp*(annot)
if bop_ff.op == ast.OpKind.BitOr:
if bop_ff.left is not None and bop_ff.left.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.left)
elif bop_ff.right is not None and bop_ff.right.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(bop_ff.right)
if ann_ff.kind() == ast.ASTKind.Subscript: ann_sub = ast.Subscript*(ann_ff)
if ann_sub is None or ann_sub.value is None or ann_sub.value.kind() != ast.ASTKind.Name: return 0
base_nm_ff: ast.Name* = ast.Name*(ann_sub.value)
if base_nm_ff.id is None: return 0
@@ -2818,11 +2845,13 @@ novirtual class AssignGenMixin:
ann_gen_base: str = None
if annot is not None:
ag_side: ast.AST* = annot
if ag_side.kind() == ast.ASTKind.BinOp:
ag_bop: ast.BinOp* = ast.BinOp*(ag_side)
if ag_bop.op == ast.OpKind.BitOr:
if ag_bop.left is not None and ag_bop.left.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.left
elif ag_bop.right is not None and ag_bop.right.kind() == ast.ASTKind.Subscript: ag_side = ag_bop.right
# Unwrap a CPtr[T] pointer layer to inspect the generic base.
if ag_side.kind() == ast.ASTKind.Subscript:
ag_wrap: ast.Subscript* = ast.Subscript*(ag_side)
if ag_wrap.value is not None and ag_wrap.value.kind() == ast.ASTKind.Name:
ag_wnm: ast.Name* = ast.Name*(ag_wrap.value)
if ag_wnm.id is not None and (string.strcmp(ag_wnm.id, "CPtr") == 0 or string.strcmp(ag_wnm.id, "rawptr") == 0):
if ag_wrap.slice is not None: ag_side = ag_wrap.slice
if ag_side.kind() == ast.ASTKind.Subscript:
ag_sub: ast.Subscript* = ast.Subscript*(ag_side)
if ag_sub.value is not None and ag_sub.value.kind() == ast.ASTKind.Name:
@@ -3677,6 +3706,11 @@ novirtual class AssignGenMixin:
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
start = i + 1
@@ -3690,6 +3724,11 @@ novirtual class AssignGenMixin:
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
se: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(seg)
if se is not None: return seg
return None
+2 -17
View File
@@ -669,21 +669,11 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
return buf
return ""
# Attribute: t.ASM_DESCR.XXX or t.XXX
# Attribute: ASM_DESCR.XXX
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
# t.ASM_DESCR.XXX
if at.value is not None and at.value.kind() == ast.ASTKind.Attribute:
inner: ast.Attribute* = ast.Attribute*(at.value)
if inner.value is not None and inner.value.kind() == ast.ASTKind.Name:
mod_nm: ast.Name* = ast.Name*(inner.value)
if mod_nm.id is not None and string.strcmp(mod_nm.id, "t") == 0:
if inner.attr is not None and string.strcmp(inner.attr, "ASM_DESCR") == 0: return self._resolve_register(at.attr)
# t.XXX shorthand form
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
mod_nm2: ast.Name* = ast.Name*(at.value)
if mod_nm2.id is not None and string.strcmp(mod_nm2.id, "t") == 0: return self._resolve_register(at.attr)
# Bare ASM_DESCR.XXX: equivalent form without the t. prefix
if mod_nm2.id is not None and string.strcmp(mod_nm2.id, "ASM_DESCR") == 0: return self._resolve_register(at.attr)
return ""
@@ -724,12 +714,7 @@ novirtual class CallGenCBuiltin(CallGenBase.CallGenBase):
if string.strcmp(nm.id, "irinp") == 0: return "LInp"
if string.strcmp(nm.id, "irout") == 0: return "LOut"
return None
if node.func.kind() != ast.ASTKind.Attribute: return None
at: ast.Attribute* = ast.Attribute*(node.func)
if at.value is None or at.value.kind() != ast.ASTKind.Name: return None
mod_nm: ast.Name* = ast.Name*(at.value)
if mod_nm.id is None or string.strcmp(mod_nm.id, "c") != 0: return None
return at.attr
return None
def _asm_name_is_mangled(self, name: str) -> int:
+9 -8
View File
@@ -1059,15 +1059,16 @@ novirtual class CallGenCtor(CallGenBase.CallGenBase):
if trans.CurAssignNode is not None and uint64(cl) == uint64(trans.CurAssignNode): ctx_annot = trans.CurAssignAnnot
if ctx_annot is None and trans.CurRetNode is not None and uint64(cl) == uint64(trans.CurRetNode): ctx_annot = trans.CurFuncRetAnnot
if ctx_annot is None: return None
# Take the generic Subscript side of the annotation (same as _prespecialize_generic_annotation:
# supports both `X[T]` and `X[T]*`).
# Take the generic Subscript side of the annotation: `X[T]` or the `CPtr[X[T]]` pointer form.
ctx_ps: ast.AST* = ctx_annot
if ctx_ps.kind() == ast.ASTKind.Subscript:
sub_wrap_ctx: ast.Subscript* = ast.Subscript*(ctx_ps)
if sub_wrap_ctx.value is not None and sub_wrap_ctx.value.kind() == ast.ASTKind.Name:
wk_nm_ctx: ast.Name* = ast.Name*(sub_wrap_ctx.value)
if wk_nm_ctx.id is not None and (string.strcmp(wk_nm_ctx.id, "CPtr") == 0 or string.strcmp(wk_nm_ctx.id, "rawptr") == 0):
if sub_wrap_ctx.slice is not None: ctx_ps = sub_wrap_ctx.slice
spec_annot: ast.Subscript* = None
if ctx_annot.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(ctx_annot)
elif ctx_annot.kind() == ast.ASTKind.BinOp:
bop_ctx: ast.BinOp* = ast.BinOp*(ctx_annot)
if bop_ctx.op == ast.OpKind.BitOr:
if bop_ctx.left is not None and bop_ctx.left.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ctx.left)
elif bop_ctx.right is not None and bop_ctx.right.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(bop_ctx.right)
if ctx_ps.kind() == ast.ASTKind.Subscript: spec_annot = ast.Subscript*(ctx_ps)
if spec_annot is None: return None
# The annotation must be the same generic class as this construction (otherwise unrelated)
sv_ctx: ast.AST* = spec_annot.value
+23 -154
View File
@@ -248,54 +248,28 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
# irinp/irout standalone calls return 0 (meaningful only inside __llvmir__ f-strings)
return mlirlite.ConstInt(pool, mlirlite.Int32(pool), 0, "0")
# Detect c.Deref / c.DerefAs / c.Addr special calls (inline IR, no call emitted)
# and coro.* coroutine interception (inline translation, VPC-M1).
# Detect coro.* coroutine interception (inline translation, VPC-M1).
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_node: ast.Attribute* = ast.Attribute*(cl.func)
if at_node.value is not None and at_node.value.kind() == ast.ASTKind.Name:
mod_nm: ast.Name* = ast.Name*(at_node.value)
is_coro_mod: int = 0
if mod_nm.id is not None and string.strcmp(mod_nm.id, "coro") == 0: is_coro_mod = 1
if mod_nm.id is not None and (string.strcmp(mod_nm.id, "c") == 0 or is_coro_mod != 0):
if string.strcmp(func_name, "Deref") == 0: return self.Gen.CallGenCBuiltin.translate_c_deref(pool, builder, mod, cl, trans)
elif string.strcmp(func_name, "DerefAs") == 0: return self.Gen.CallGenCBuiltin.translate_c_derefas(pool, builder, mod, cl, trans)
elif string.strcmp(func_name, "Addr") == 0: return self.Gen.CallGenCBuiltin.translate_c_addr(pool, builder, mod, cl, trans)
elif string.strcmp(func_name, "Set") == 0:
# c.Set(a, b) = a = b (assignment, not pointer deref). Treating it as a plain
# call would emit @Set and fail to link (undefined reference to `Set').
return self.Gen.CallGenCBuiltin.translate_c_set(pool, builder, mod, cl, trans)
elif string.strcmp(func_name, "Load") == 0: return self.Gen.CallGenCBuiltin.translate_c_load(pool, builder, mod, cl, trans)
elif string.strcmp(func_name, "Asm") == 0:
# translate_c_asm returns None for void: set the hit flag so translate_call
# does not fall through to the external function path.
if hit_box is not None: hit_box[0] = 1
return self.Gen.CallGenCBuiltin.translate_c_asm(pool, builder, mod, cl, trans)
elif string.strcmp(func_name, "LLVMIR") == 0:
# c.LLVMIR always returns void (None): set the hit flag
if hit_box is not None: hit_box[0] = 1
return self.Gen.CallGenCBuiltin.translate_c_llvmir(pool, builder, mod, cl, trans)
elif string.strcmp(func_name, "LInp") == 0 or string.strcmp(func_name, "LOut") == 0:
# c.LInp/c.LOut standalone calls return 0 (only meaningful in c.LLVMIR f-strings)
return mlirlite.ConstInt(pool, mlirlite.Int32(pool), 0, "0")
elif string.strcmp(func_name, "CIf") == 0 or string.strcmp(func_name, "CIfdef") == 0 or string.strcmp(func_name, "CIfndef") == 0 or string.strcmp(func_name, "CElif") == 0 or string.strcmp(func_name, "CElse") == 0 or string.strcmp(func_name, "CEndif") == 0:
# The c.CIf-family spellings were removed: report the replacement.
# (There is no runtime fallback: compile-time conditions use cif/celif/celse.)
self.Gen.TypeGenErr.fatal_error(cl, "c.CIf/c.CIfdef/c.CIfndef/c.CElif/c.CElse/c.CEndif were removed; use the cif/celif/celse statements and the defined(NAME) predicate instead (e.g. `cif defined(NAME):` ... `celse:` ...)", "NameError")
return None
elif is_coro_mod != 0 and string.strcmp(func_name, "coro_run") == 0:
if is_coro_mod != 0:
if string.strcmp(func_name, "coro_run") == 0:
# coro.coro_run(frame): inline translation (resume loop until done, returns the ret slot).
# Note: translate_coro_* take (builder, pool, mod, cl, trans), unlike the c.* branches.
# Note: translate_coro_* take (builder, pool, mod, cl, trans).
return self.Gen.AsyncGen.translate_coro_run(builder, pool, mod, cl, trans)
elif is_coro_mod != 0 and string.strcmp(func_name, "coro_destroy") == 0:
elif string.strcmp(func_name, "coro_destroy") == 0:
# coro.coro_destroy(frame): return the frame to the pool (M7 free-list)
return self.Gen.AsyncGen.translate_coro_destroy(builder, pool, mod, cl, trans)
elif is_coro_mod != 0 and string.strcmp(func_name, "coro_exception") == 0:
elif string.strcmp(func_name, "coro_exception") == 0:
# coro.coro_exception(frame): return the frame exc slot (M4)
return self.Gen.AsyncGen.translate_coro_exception(builder, pool, mod, cl, trans)
elif is_coro_mod != 0 and string.strcmp(func_name, "coro_exception_msg") == 0:
elif string.strcmp(func_name, "coro_exception_msg") == 0:
# coro.coro_exception_msg(frame): return the frame msg slot (M7)
return self.Gen.AsyncGen.translate_coro_exception_msg(builder, pool, mod, cl, trans)
elif is_coro_mod != 0 and string.strcmp(func_name, "coro_gather") == 0:
elif string.strcmp(func_name, "coro_gather") == 0:
# coro.coro_gather(frames...): advance all coroutines concurrently (M4)
return self.Gen.AsyncGen.translate_coro_gather(builder, pool, mod, cl, trans)
return None
@@ -362,112 +336,15 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
return None
def _tc_h4_type_casts(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, cl: ast.Call*, funcs_ptr: rawptr, func_count: int, trans: HT.Translator*, func_name: str, cargs: list[ast.AST*]*, can: sizet) -> mlirlite.Value*:
"""t.XXX / typedef type casts; returns None when unmatched."""
# Detect t.XXX type casts (uint64(ptr), rawptr(val), int(val), ...): when func is t.XXX and
# XXX is a known type, treat it as a cast; once confirmed, never fall through to cross-module calls.
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
tc_at: ast.Attribute* = ast.Attribute*(cl.func)
if tc_at.value is not None and tc_at.value.kind() == ast.ASTKind.Name:
tc_mod_nm: ast.Name* = ast.Name*(tc_at.value)
if tc_mod_nm.id is not None and string.strcmp(tc_mod_nm.id, "t") == 0:
# t.CType(value, TypeName, rawptr) casts value to TypeName*; TypeName is a type
# name (not a value), so it must be handled explicitly or it falls through to a FATAL.
if string.strcmp(func_name, "CType") == 0 and can >= 2:
cty_node: ast.AST* = cargs.get(1)
ctype_target: mlirlite.LLVMType* = None
if cty_node is not None:
cti_imp: str = None
cti_fi: str = None
if trans is not None:
cti_imp = trans._imported_modules
cti_fi = trans._from_imports
ctype_target = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cty_node, cti_imp, cti_fi, trans, 0)
if ctype_target is not None:
if can == 3:
cty_arg3: ast.AST* = cargs.get(2)
cty_is_cptr: int = 0
if cty_arg3 is not None:
# Form 1: rawptr (Attribute)
if cty_arg3.kind() == ast.ASTKind.Attribute:
cty_at3: ast.Attribute* = ast.Attribute*(cty_arg3)
if cty_at3.attr is not None and string.strcmp(cty_at3.attr, "CPtr") == 0:
if cty_at3.value is not None and cty_at3.value.kind() == ast.ASTKind.Name:
cty_nm3: ast.Name* = ast.Name*(cty_at3.value)
if cty_nm3.id is not None and string.strcmp(cty_nm3.id, "t") == 0: cty_is_cptr = 1
# Form 2: bare CPtr
elif cty_arg3.kind() == ast.ASTKind.Name:
cty_bn3: ast.Name* = ast.Name*(cty_arg3)
if cty_bn3.id is not None and string.strcmp(cty_bn3.id, "CPtr") == 0: cty_is_cptr = 1
if cty_is_cptr != 0:
# CVoid* -> i8* (void* is represented as i8* in LLVM)
match ctype_target:
case mlirlite.LLVMType.Void():
ctype_target = mlirlite.Int8(pool)
case _:
pass
ctype_target = mlirlite.Ptr(pool, ctype_target)
cty_arg0: ast.AST* = cargs.get(0)
if cty_arg0 is not None:
cty_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cty_arg0, funcs_ptr, func_count, trans)
if cty_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, cty_val, ctype_target, cty_arg0, trans)
# t.CType confirmed: even if type resolution fails, do not fall through to a cross-module FATAL
return None
# CConst is a const marker: CConst(val, rawptr) -> Ptr(i8) (const void*). Without
# this it emits an undefined @CConst; single-arg CConst(x) returns the value unchanged.
if string.strcmp(func_name, "CConst") == 0:
if can >= 2:
cconst_ptr_ty: mlirlite.LLVMType* = mlirlite.Ptr(pool, mlirlite.Int8(pool))
cconst_arg: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans)
if cconst_arg is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, cconst_arg, cconst_ptr_ty, cargs.get(0), trans)
elif can == 1: return self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans)
return None
target_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_t_type(pool, func_name)
if target_ty is not None:
tc_effective_can: sizet = can
# Two-arg form: int(val, rawptr) -> Ptr(target_ty). void* is illegal in LLVM,
# so CVoid* must be represented as i8*.
if can == 2:
tc_arg2_node: ast.AST* = cargs.get(1)
tc_arg2_is_cptr: int = 0
if tc_arg2_node is not None:
# Form 1: rawptr (Attribute form)
if tc_arg2_node.kind() == ast.ASTKind.Attribute:
tc_arg2_at: ast.Attribute* = ast.Attribute*(tc_arg2_node)
if tc_arg2_at.attr is not None and string.strcmp(tc_arg2_at.attr, "CPtr") == 0:
if tc_arg2_at.value is not None and tc_arg2_at.value.kind() == ast.ASTKind.Name:
tc_arg2_nm: ast.Name* = ast.Name*(tc_arg2_at.value)
if tc_arg2_nm.id is not None and string.strcmp(tc_arg2_nm.id, "t") == 0: tc_arg2_is_cptr = 1
# Form 2: bare CPtr (usable after `from t import CPtr`, e.g. t.CXXX(x, CPtr))
elif tc_arg2_node.kind() == ast.ASTKind.Name:
tc_arg2_bn: ast.Name* = ast.Name*(tc_arg2_node)
if tc_arg2_bn.id is not None and string.strcmp(tc_arg2_bn.id, "CPtr") == 0: tc_arg2_is_cptr = 1
if tc_arg2_is_cptr != 0:
# CVoid* -> i8* (void* is represented as i8* in LLVM)
match target_ty:
case mlirlite.LLVMType.Void():
target_ty = mlirlite.Int8(pool)
case _:
pass
target_ty = mlirlite.Ptr(pool, target_ty)
tc_effective_can = 1
if tc_effective_can == 1:
tc_arg_node: ast.AST* = cargs.get(0)
if tc_arg_node is not None:
# Pass target_ty as the 8th arg: otherwise constants pick their width from
# the i32 range and uint64(-1) materializes as i32 -1 then zext (N1).
tc_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, tc_arg_node, None, 0, trans, target_ty)
if tc_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, tc_arg_val, target_ty, tc_arg_node, trans)
# t.XXX cast confirmed but arg shape unsupported: return None, do not fall through to FATAL
return None
"""Builtin / typedef type casts; returns None when unmatched."""
# Module-qualified typedef cast (posix.mode_t(mode), win32.win32base.HANDLE(v), ...): func is
# Attribute(Name(module), typedef); non-typedef names still go to the module function call.
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
mt_at: ast.Attribute* = ast.Attribute*(cl.func)
if mt_at.value is not None and mt_at.value.kind() == ast.ASTKind.Name:
mt_mod_nm: ast.Name* = ast.Name*(mt_at.value)
if mt_mod_nm.id is not None and string.strcmp(mt_mod_nm.id, "t") != 0 and string.strcmp(mt_mod_nm.id, "c") != 0:
mod_td_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_t_type(pool, func_name)
if mt_mod_nm.id is not None:
mod_td_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.map_builtin_type(pool, func_name)
if mod_td_ty is None:
mod_td_str: str = self.Gen.TypeGenCType.lookup_ctypedef(func_name)
if mod_td_str is not None and self.Gen.TypeGenCType.is_ctypedef_found() != 0:
@@ -483,26 +360,26 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
mt_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cargs.get(0), funcs_ptr, func_count, trans, mod_td_ty)
if mt_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, mt_arg_val, mod_td_ty, cargs.get(0), trans)
# Bare typedef casts (BYTEPTR, HANDLE, CHARPTR, ...): all CTypedef names and t/c primitives
# Bare typedef casts (BYTEPTR, HANDLE, CHARPTR, ...): all CTypedef names and builtin primitives
# are castable. In generic contexts T(x) first resolves T, then goes through resolve_typedef_or_map.
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
bare_fn_name: str = self.Gen.TypeGenBase.resolve_generic_type_name(func_name)
bare_ty: mlirlite.LLVMType* = None
# Builtin names resolved from generic context (T->"double"/"float"): map_t_type only knows
# t.CDouble, and float must map to t.CFloat (f32), not f64.
# Builtin names resolved from generic context (T->"double"/"float"): float must map to
# f32, not f64.
if string.strcmp(bare_fn_name, "double") == 0: bare_ty = mlirlite.Double(pool)
elif string.strcmp(bare_fn_name, "float") == 0: bare_ty = mlirlite.Float(pool)
else:
# Prefer the CTypedef dynamic table (library typedef casts); on miss fall back to
# map_t_type, which (after phase-2 convergence) no longer contains stdint aliases.
# map_builtin_type.
bare_imp_c: str = None
bare_fi_c: str = None
if trans is not None:
bare_imp_c = trans._imported_modules
bare_fi_c = trans._from_imports
bare_ty = self.Gen.TypeGenResolve.resolve_typedef_or_map(pool, bare_fn_name, bare_imp_c, bare_fi_c)
# Names substituted from generic context (T->"int") are unknown to map_t_type, so fall
# back to resolve_annotation_type (supports t.CXxx / struct class names / aliases).
# Names substituted from generic context (T->"int") are unknown to map_builtin_type, so fall
# back to resolve_annotation_type (supports builtin names / struct class names / aliases).
if bare_ty is None and string.strcmp(bare_fn_name, func_name) != 0:
gp_ast: ast.AST* = self.Gen.TypeGenResolve._parse_type_string(pool, bare_fn_name, 0)
if gp_ast is not None:
@@ -518,17 +395,10 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
bare_arg2_node: ast.AST* = cargs.get(1)
bare_arg2_is_cptr: int = 0
if bare_arg2_node is not None:
# Form 1: rawptr (Attribute form)
if bare_arg2_node.kind() == ast.ASTKind.Attribute:
bare_arg2_at: ast.Attribute* = ast.Attribute*(bare_arg2_node)
if bare_arg2_at.attr is not None and string.strcmp(bare_arg2_at.attr, "CPtr") == 0:
if bare_arg2_at.value is not None and bare_arg2_at.value.kind() == ast.ASTKind.Name:
bare_arg2_nm: ast.Name* = ast.Name*(bare_arg2_at.value)
if bare_arg2_nm.id is not None and string.strcmp(bare_arg2_nm.id, "t") == 0: bare_arg2_is_cptr = 1
# Form 2: bare CPtr (usable after `from t import CPtr`)
elif bare_arg2_node.kind() == ast.ASTKind.Name:
# Builtin pointer marker: CPtr / rawptr
if bare_arg2_node.kind() == ast.ASTKind.Name:
bare_arg2_bn: ast.Name* = ast.Name*(bare_arg2_node)
if bare_arg2_bn.id is not None and string.strcmp(bare_arg2_bn.id, "CPtr") == 0: bare_arg2_is_cptr = 1
if bare_arg2_bn.id is not None and (string.strcmp(bare_arg2_bn.id, "CPtr") == 0 or string.strcmp(bare_arg2_bn.id, "rawptr") == 0): bare_arg2_is_cptr = 1
if bare_arg2_is_cptr != 0:
# CVoid* -> i8* (void* is represented as i8* in LLVM)
match bare_ty:
@@ -668,8 +538,8 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
if szf_mod is not None:
string.strncpy(szf_mod, sz_full_name, szf_mod_len)
szf_mod[szf_mod_len] = '\0'
# Exempt t/c builtins (int etc. are already handled by the builtin/size table)
if string.strcmp(szf_mod, "t") != 0 and string.strcmp(szf_mod, "c") != 0:
# Module-qualified builtin names are already handled by the builtin/size table.
if szf_mod is not None:
szf_sha1: str = self.Gen.StructGen._resolve_module_sha1(szf_mod, trans._from_imports if trans is not None else None)
if szf_sha1 is not None:
szf_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_module(sz_cls_name, szf_sha1)
@@ -1247,8 +1117,7 @@ novirtual class CallGenDispatch(CallGenBase.CallGenBase):
if expr_root is not None and expr_root.kind() == ast.ASTKind.Name:
expr_root_nm: str = ast.Name*(expr_root).id
if expr_root_nm is not None:
if string.strcmp(expr_root_nm, "t") == 0 or string.strcmp(expr_root_nm, "c") == 0: is_mod_path_expr = 1
elif trans._imported_modules is not None:
if trans._imported_modules is not None:
if self.Gen.ImportsGen.is_module_imported(trans._imported_modules, expr_root_nm) != 0: is_mod_path_expr = 1
if evk != ast.ASTKind.Name and is_mod_path_expr == 0:
# Translate the receiver expression (the result should be Ptr(Struct))
+4 -12
View File
@@ -324,16 +324,8 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
viperlib.snprintf(gt_err, 256, "Generic class '%s': template not found; the defining module may not be imported", gen_class_name)
self.Gen.TypeGenErr.fatal_error(gen_nm_node, gt_err)
self.Gen.TypeGenErr.fatal_error(gen_nm_node, "Generic class template not found")
if func_name is None:
# BinOp as the Call func: a union-type annotation used as a type cast, (A*)(value).
if cl.func is not None and cl.func.kind() == ast.ASTKind.BinOp and trans is not None:
binop_ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0)
if binop_ty is not None:
binop_args: list[ast.AST*]* = cl.args
if binop_args is not None and len(binop_args) == 1:
binop_arg_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, binop_args.get(0), funcs_ptr, func_count, trans)
if binop_arg_val is not None: return self.Gen.CallGenCtor._translate_t_type_cast(pool, builder, binop_arg_val, binop_ty)
return None
# Union type casts were retired; only Subscript / Name callees remain.
if func_name is None: return None
# c.Asm/c.LLVMIR special calls return None when void but are handled, so use hit_box to
# separate "missed"; otherwise they fall through to the external-function path and mis-report.
@@ -1291,8 +1283,8 @@ novirtual class CallGenMixin(CallGenBase.CallGenBase):
viperlib.snprintf(mangled, mangled_len, "%s.%s", mod_sha1, func_name)
call_name = mangled
else:
# t and c are internal libraries exempt from SHA1 lookup: use bare-name calls.
if string.strcmp(xmod_path, "t") == 0 or string.strcmp(xmod_path, "c") == 0: call_name = func_name
# Only real modules reach this path; a missing module segment falls back to a bare name.
if xmod_path is None: call_name = func_name
else:
# Check whether it is an imported module name (e.g. BuildPipeline.run_pipeline): if imported
# but its SHA1 is unregistered the module was not compiled, so error out instead of a misleading from_imports lookup.
+13 -37
View File
@@ -147,11 +147,7 @@ novirtual class ClassGenBase:
while alias_orig[og_len_ce] != ' ' and alias_orig[og_len_ce] != '\0': og_len_ce += 1
if og_len_ce == 5 and string.strncmp(alias_orig, "CEnum", 5) == 0: return 1
return 0
# Attribute(t.CEnum) form.
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(base_node)
if at.attr is not None and string.strcmp(at.attr, "CEnum") == 0: return 1
return 0
# Attribute form was retired together with the t module prefix.
return 0
def _is_enum_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
"""Return 1 if the ClassDef is a CEnum (subclasses t.CEnum or uses @t.CEnum)."""
@@ -185,11 +181,7 @@ novirtual class ClassGenBase:
while alias_orig[og_len_cu] != ' ' and alias_orig[og_len_cu] != '\0': og_len_cu += 1
if og_len_cu == 6 and string.strncmp(alias_orig, "CUnion", 6) == 0: return 1
return 0
# Attribute(t.CUnion) form.
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(base_node)
if at.attr is not None and string.strcmp(at.attr, "CUnion") == 0: return 1
return 0
# Attribute form was retired together with the t module prefix.
return 0
def _is_union_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
"""Return 1 if the ClassDef is a CUnion (subclasses t.CUnion or uses @t.CUnion)."""
@@ -223,11 +215,7 @@ novirtual class ClassGenBase:
while alias_orig[og_len_cr] != ' ' and alias_orig[og_len_cr] != '\0': og_len_cr += 1
if og_len_cr == 5 and string.strncmp(alias_orig, "REnum", 5) == 0: return 1
return 0
# Attribute(t.REnum) form.
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(base_node)
if at.attr is not None and string.strcmp(at.attr, "REnum") == 0: return 1
return 0
# Attribute form was retired together with the t module prefix.
return 0
def _is_renum_class(self, cd: ast.ClassDef*, trans: HT.Translator* = None) -> int:
"""Return 1 if the ClassDef is a REnum (subclasses t.REnum or uses @t.REnum)."""
@@ -245,7 +233,7 @@ novirtual class ClassGenBase:
if self._is_crenum_base(base_node, trans) == 1: return 1
return 0
def _has_decorator(self, decorator_list: list[ast.AST*]*, deco_name: str) -> int:
"""Return 1 if decorator_list contains the given decorator name."""
"""Return 1 if decorator_list contains the given bare builtin decorator name."""
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
@@ -253,16 +241,7 @@ novirtual class ClassGenBase:
deco: ast.AST* = decorator_list.get(di)
if deco is None: continue
k: int = deco.kind()
# @t.NoVTable form (Attribute).
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(deco)
if at.attr is not None and string.strcmp(at.attr, deco_name) == 0:
# Check that value is Name('t').
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
vn: ast.Name* = ast.Name*(at.value)
if vn.id is not None and string.strcmp(vn.id, "t") == 0: return 1
return 0
# @NoVTable form (Name; from t import NoVTable).
# @NoVTable / @CVTable / @TLS / @CStatic form (bare builtin Name).
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(deco)
if nm.id is not None and string.strcmp(nm.id, deco_name) == 0: return 1
@@ -290,10 +269,10 @@ novirtual class ClassGenBase:
k: int = deco.kind()
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(deco)
# @c.Attribute(t.attr.xxx) form: func is Attribute(c, "Attribute").
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: continue
at0: ast.Attribute* = ast.Attribute*(cl.func)
if at0.attr is None or string.strcmp(at0.attr, "Attribute") != 0: continue
# @Attribute(attr.xxx) form: func is the bare builtin Name("Attribute").
if cl.func is None or cl.func.kind() != ast.ASTKind.Name: continue
at0: ast.Name* = ast.Name*(cl.func)
if at0.id is None or string.strcmp(at0.id, "Attribute") != 0: continue
if cl.args is None: continue
an: sizet = len(cl.args)
for ai in range(int(an)):
@@ -319,7 +298,7 @@ novirtual class ClassGenBase:
return int(cn.int_val)
return 0
def _class_aligned_value(self, decorator_list: list[ast.AST*]*) -> int:
"""Return the alignment bytes from @c.Attribute(t.attr.aligned(N)); 0 when absent."""
"""Return the alignment bytes from @Attribute(attr.aligned(N)); 0 when absent."""
if decorator_list is None: return 0
dn: sizet = len(decorator_list)
if dn == 0: return 0
@@ -328,9 +307,9 @@ novirtual class ClassGenBase:
if deco is None: continue
if deco.kind() == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(deco)
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute: continue
at0: ast.Attribute* = ast.Attribute*(cl.func)
if at0.attr is None or string.strcmp(at0.attr, "Attribute") != 0: continue
if cl.func is None or cl.func.kind() != ast.ASTKind.Name: continue
at0: ast.Name* = ast.Name*(cl.func)
if at0.id is None or string.strcmp(at0.id, "Attribute") != 0: continue
if cl.args is None: continue
an: sizet = len(cl.args)
for ai in range(int(an)):
@@ -366,9 +345,6 @@ novirtual class ClassGenBase:
vn: ast.Name* = ast.Name*(at.value)
is_prop_name: int = 0
if vn.id is not None and string.strcmp(vn.id, "property") == 0: is_prop_name = 1
# @t.TLS one-time init marker (mirrors TPC HandlesFunctions):
# value must be Name('t'); bare @TLS is handled by the Name branch.
if vn.id is not None and string.strcmp(vn.id, "t") == 0 and string.strcmp(at.attr, "TLS") == 0: meta |= META_TLS
# Recognize @property.setter and @x.setter/getter/deleter.
if is_prop_name != 0 or string.strcmp(at.attr, "setter") == 0 or string.strcmp(at.attr, "getter") == 0 or string.strcmp(at.attr, "deleter") == 0:
if string.strcmp(at.attr, "setter") == 0: meta |= GenTypes.META_PROP_SETTER
+35 -27
View File
@@ -42,7 +42,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
# [enum bool rule] `t.CBool` and bare `bool` both materialize as 8 bits in enum member annotations,
# else the same member would split into i1 / i8; the global `t.CBool` stays i1 (only enums differ).
if string.strcmp(at.attr, "CBool") == 0: return mlirlite.Int8(pool)
# Other t.CXxx types: unified (CTypedef dynamic table first, fall back to map_t_type)
# Other t.CXxx types: unified (CTypedef dynamic table first, fall back to map_builtin_type)
ty: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_typedef_or_map(pool, at.attr, imported_modules, from_imports)
if ty is not None: return ty
# [S4 fail-fast] annotated but unresolvable -> no silent i32 fallback
@@ -71,23 +71,24 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {nm.id}", "NameError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
# BinOp node: t.State | int8 (recurse to find the concrete type)
# Marker wrapper (CConst[T] / CVolatile[T] / ...): resolve the wrapped type.
if k == ast.ASTKind.Subscript:
sub_em: ast.Subscript* = ast.Subscript*(ann_node)
if sub_em.value is not None and sub_em.value.kind() == ast.ASTKind.Name:
em_nm: ast.Name* = ast.Name*(sub_em.value)
if em_nm.id is not None and (string.strcmp(em_nm.id, "CConst") == 0 or string.strcmp(em_nm.id, "CVolatile") == 0 \
or string.strcmp(em_nm.id, "BigEndian") == 0 or string.strcmp(em_nm.id, "LittleEndian") == 0 \
or string.strcmp(em_nm.id, "State") == 0 or string.strcmp(em_nm.id, "CStatic") == 0 \
or string.strcmp(em_nm.id, "CExport") == 0 or string.strcmp(em_nm.id, "CExtern") == 0 \
or string.strcmp(em_nm.id, "CRef") == 0 or string.strcmp(em_nm.id, "CTempRef") == 0):
return self._resolve_enum_member_type(pool, sub_em.slice, imported_modules, from_imports)
self.Gen.TypeGenErr.fatal_type_error(ann_node, "unsupported enum member type: only scalar types and their markers are allowed")
return mlirlite.Int32(pool)
# BinOp nodes are never enum member types (`T1 | T2` unions retired).
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(ann_node)
if bop.op == ast.OpKind.BitOr:
left_ty: mlirlite.LLVMType* = self._resolve_enum_member_type(pool, bop.left, imported_modules, from_imports)
# Skip t.State (the i32 default); prefer a concrete type
left_bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(left_ty)
if left_bits != 0 and not self._is_state_annotation(bop.left): return left_ty
right_ty: mlirlite.LLVMType* = self._resolve_enum_member_type(pool, bop.right, imported_modules, from_imports)
if right_ty is not None and not self._is_state_annotation(bop.right): return right_ty
# Both sides are State or undecidable; return the wider one
right_bits: int = self.Gen.TypeGenErr.get_llvm_type_bits(right_ty)
if left_bits >= right_bits: return left_ty
return right_ty
# fail-fast: BinOp but not BitOr (unknown union op); no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {self.Gen.TypeGenResolve.annotation_to_str(ann_node)}", "TypeError")
return mlirlite.Int32(pool) # unreachable (fatal_error already exited)
self.Gen.TypeGenErr.fatal_type_error(ann_node, "enum member type uses the retired `T1 | T2` union; write the concrete type directly")
return mlirlite.Int32(pool)
# fail-fast: unknown annotation node kind; no silent i32 fallback
self.Gen.TypeGenErr.fatal_error(ann_node, f"failed to resolve enum member type annotation: {self.Gen.TypeGenResolve.annotation_to_str(ann_node)}", "TypeError")
@@ -133,17 +134,12 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
return 0
if k == ast.ASTKind.Subscript:
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is not None: return self._enum_annotation_name_is_ptr(sub.value)
if sub.value is not None and self._enum_annotation_name_is_ptr(sub.value) != 0: return 1
if sub.slice is not None: return self._enum_annotation_name_is_ptr(sub.slice)
return 0
return 0
def _enum_member_annotation_is_ptr(self, ann_node: ast.AST*) -> int:
"""Whether an enum member annotation (checking each union side) contains a pointer type, 1=yes / 0=no"""
if ann_node is None: return 0
if ann_node.kind() == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(ann_node)
if bop.op != ast.OpKind.BitOr: return 0
if self._enum_member_annotation_is_ptr(bop.left) == 1: return 1
return self._enum_member_annotation_is_ptr(bop.right)
"""Whether an enum member annotation contains a pointer type, 1=yes / 0=no"""
return self._enum_annotation_name_is_ptr(ann_node)
def _extract_int_value(self, node: ast.AST*) -> int64:
"""Extract an integer value from an AST node, 0 on failure"""
@@ -266,7 +262,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
tgt: ast.Name* = ast.Name*(aa.target)
if tgt.id is None: continue
# [enum spec fail-fast] Members may not be pointer types: t.CPtr / CRef /
# [enum spec fail-fast] Members may not be pointer types: rawptr / CRef /
# CTempRef / CAutoPtr / rawptr[...] / str / bytes and unions carrying a pointer. Checked before type resolution.
if self._enum_member_annotation_is_ptr(aa.annotation) == 1:
self.Gen.TypeGenErr.fatal_error(aa, f"enum member '{tgt.id}' cannot be a pointer type (enum values only support integer member types)", "TypeError")
@@ -601,6 +597,18 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
# analysis; the hardcoded size table below is only a fallback for t/c core primitives.
dyn_sz: int = self.Gen.TypeGenResolve.get_type_size_bytes(pool, type_name, None, None)
if dyn_sz > 0: return dyn_sz
# Builtin short names
if string.strcmp(type_name, "sizet") == 0 or string.strcmp(type_name, "size_t") == 0: return 8
if string.strcmp(type_name, "int8") == 0 or string.strcmp(type_name, "uint8") == 0: return 1
if string.strcmp(type_name, "uchar") == 0: return 1
if string.strcmp(type_name, "int16") == 0 or string.strcmp(type_name, "uint16") == 0: return 2
if string.strcmp(type_name, "short") == 0 or string.strcmp(type_name, "ushort") == 0: return 2
if string.strcmp(type_name, "int32") == 0 or string.strcmp(type_name, "uint32") == 0: return 4
if string.strcmp(type_name, "int") == 0 or string.strcmp(type_name, "uint") == 0: return 4
if string.strcmp(type_name, "long") == 0 or string.strcmp(type_name, "ulong") == 0: return 4
if string.strcmp(type_name, "int64") == 0 or string.strcmp(type_name, "uint64") == 0: return 8
if string.strcmp(type_name, "longlong") == 0 or string.strcmp(type_name, "ulonglong") == 0: return 8
if string.strcmp(type_name, "ptrdifft") == 0: return 8
# Builtin types
if string.strcmp(type_name, "int") == 0: return 4
if string.strcmp(type_name, "CInt") == 0: return 4
@@ -793,7 +801,7 @@ novirtual class ClassGenEnum(ClassGenBase.ClassGenBase):
# Register into HandlesStruct.
# A forward-declared placeholder for THIS class may have been created *during the variant walk*
# above: a variant field like `Pointee: LLVMType*` is `BinOp(LLVMType | t.CPtr)` (parser's
# above: a variant field like `Pointee: LLVMType*` is `BinOp(LLVMType | rawptr)` (parser's
# _make_ptr_type expands the C-style suffix into a union, NOT into `rawptr[...]`), so resolving it
# resolves the class name and calls ensure_forward_class. register_struct de-duplicates by Ty
# pointer, so it would add a SECOND same-named entry and get_struct_type/find_struct (first match
+2 -2
View File
@@ -318,10 +318,10 @@ novirtual class ClassGenGeneric(ClassGenBase.ClassGenBase):
if sign == TSpec.TS_SIGN_SIGNED: return "int64"
return None
# ---- Floats: exact bit width (no signedness concept, unambiguous) ----
if string.strcmp(buf, "half") == 0: return "t.CFloat16T"
if string.strcmp(buf, "half") == 0: return "float16"
if string.strcmp(buf, "float") == 0: return "float"
if string.strcmp(buf, "double") == 0: return "double"
if string.strcmp(buf, "fp128") == 0: return "t.CFloat128T"
if string.strcmp(buf, "fp128") == 0: return "float128"
return None
def infer_scalar_type_arg_from_llvm_ty(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*, sign: int) -> str:
+1 -1
View File
@@ -694,7 +694,7 @@ novirtual class ClassGenVTable(ClassGenBase.ClassGenBase):
arg_m: ast.Arg* = ast.Arg*(alist.get(ai_m))
mm: int = 0
if arg_m is not None and arg_m.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg_m.annotation, "CVoid", imported_modules) == 0:
if self.Gen.TypeGenResolve._is_builtin_marker(arg_m.annotation, "CVoid", imported_modules) == 0:
mm = self.Gen.TypeGenResolve.ref_param_mode(arg_m.annotation, imported_modules)
if mm == 1: mode_buf[mode_cnt] = char(49)
elif mm == 2: mode_buf[mode_cnt] = char(50)
+64 -56
View File
@@ -65,6 +65,42 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
def _is_t_type_object_name(self, name: str) -> int:
"""Check whether name is a t.py type-object name (CPtr/CInt/CDouble, ...)."""
if name is None: return 0
# builtin short type names (int8/uint64/sizet/...)
if string.strcmp(name, "sizet") == 0: return 1
if string.strcmp(name, "size_t") == 0: return 1
if string.strcmp(name, "int8") == 0: return 1
if string.strcmp(name, "int16") == 0: return 1
if string.strcmp(name, "int32") == 0: return 1
if string.strcmp(name, "int64") == 0: return 1
if string.strcmp(name, "uint8") == 0: return 1
if string.strcmp(name, "uint16") == 0: return 1
if string.strcmp(name, "uint32") == 0: return 1
if string.strcmp(name, "uint64") == 0: return 1
if string.strcmp(name, "intfast8") == 0: return 1
if string.strcmp(name, "intfast16") == 0: return 1
if string.strcmp(name, "intfast32") == 0: return 1
if string.strcmp(name, "intfast64") == 0: return 1
if string.strcmp(name, "uintfast8") == 0: return 1
if string.strcmp(name, "uintfast16") == 0: return 1
if string.strcmp(name, "uintfast32") == 0: return 1
if string.strcmp(name, "uintfast64") == 0: return 1
if string.strcmp(name, "intleast8") == 0: return 1
if string.strcmp(name, "intleast16") == 0: return 1
if string.strcmp(name, "intleast32") == 0: return 1
if string.strcmp(name, "intleast64") == 0: return 1
if string.strcmp(name, "uintleast8") == 0: return 1
if string.strcmp(name, "uintleast16") == 0: return 1
if string.strcmp(name, "uintleast32") == 0: return 1
if string.strcmp(name, "uintleast64") == 0: return 1
if string.strcmp(name, "uchar") == 0: return 1
if string.strcmp(name, "short") == 0: return 1
if string.strcmp(name, "ushort") == 0: return 1
if string.strcmp(name, "uint") == 0: return 1
if string.strcmp(name, "long") == 0: return 1
if string.strcmp(name, "ulong") == 0: return 1
if string.strcmp(name, "longlong") == 0: return 1
if string.strcmp(name, "ulonglong") == 0: return 1
if string.strcmp(name, "ptrdifft") == 0: return 1
# type object names in t.py (PascalCase, initial uppercase)
if string.strcmp(name, "CPtr") == 0: return 1
if string.strcmp(name, "CInt") == 0: return 1
@@ -217,36 +253,16 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
if use_ty is None: use_ty = mlirlite.Int8(pool)
# ensure cross-module struct types in use_ty have an opaque declaration
mlirlite.module_ensure_opaque_for_type(mod, pool, use_ty)
# special case: type objects in t.py (CPtr/CInt/CDouble etc.). t.py is not compiled but
# these objects are referenced at runtime, so emit a linkonce_odr definition with null init.
is_t_type_obj: int = 0
if string.strcmp(mod_path_w, "t") == 0: is_t_type_obj = self._is_t_type_object_name(at.attr)
# a class name is not a module-level global: registered classes/structs/REnums are always
# handled by the class/REnum paths, never manufactured into an external global here.
if is_t_type_obj == 0 and self.Gen.StructGen.find_struct(at.attr) is not None: return None
# fallback declaration name: t type objects (t.py uncompiled, no sha1) keep the bare
# linkonce_odr name; other module attributes carry the target module's SHA1 prefix.
gv_name_ba: str = at.attr
if is_t_type_obj == 0: gv_name_ba = self.Gen.FuncGenBase._mangle_name_with_sha1(self._lookup_module_sha1(mod_path_w), at.attr)
if self.Gen.StructGen.find_struct(at.attr) is not None: return None
# fallback declaration name: other module attributes carry the target module's SHA1 prefix.
gv_name_ba: str = self.Gen.FuncGenBase._mangle_name_with_sha1(self._lookup_module_sha1(mod_path_w), at.attr)
ext_gv: mlirlite.GlobalVariable* = mlirlite.new_global_variable(pool, gv_name_ba, use_ty)
if ext_gv is not None:
if is_t_type_obj != 0:
# t.CPtr etc.: create a definition with a null initializer (linkonce_odr)
ext_gv.Linkage = "linkonce_odr"
# initializer text for i8* null
null_init: bytes = pool.alloc(8)
if null_init is not None:
null_init[0] = 'n'
null_init[1] = 'u'
null_init[2] = 'l'
null_init[3] = 'l'
null_init[4] = '\0'
ext_gv.Initializer = null_init
else: ext_gv.Initializer = None
else:
# regular module attribute: external declaration (definition provided elsewhere)
ext_gv.Linkage = "external"
ext_gv.Initializer = None
# regular module attribute: external declaration (definition provided elsewhere)
ext_gv.Linkage = "external"
ext_gv.Initializer = None
mlirlite.module_add_global(mod, ext_gv)
return self.make_global_ref(pool, gv_name_ba, use_ty)
def _lookup_module_sha1(self, mod_name: str) -> str:
@@ -256,32 +272,19 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
# module attribute (sys._mbuddy → global), then struct field (GEP + load). Name bases skip the struct load.
# _get_cderef_attr_base - base pointer of a c.Deref(ptr).attr access (like *p.a → p->a): return the ptr
# itself, not the loaded struct value; None for non-c.Deref forms (_deref_if_ptr_ptr resolves the level).
# _get_cderef_attr_base - base pointer of a `(*ptr).attr` access (like p->a): return the ptr
# itself, not the loaded struct value; None for non-deref forms (_deref_if_ptr_ptr resolves the level).
def _get_cderef_attr_base(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, at_value: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Return the ptr itself for a dereferenced attribute base (`c.Deref(ptr).attr` / `(*ptr).attr`), else None (no load)."""
"""Return the ptr itself for a dereferenced attribute base (`(*ptr).attr`), else None (no load)."""
if at_value is None: return None
# New syntax form: `(*ptr).attr` is AST UnaryOp(OpKind.Deref) — identical semantics to the legacy
# `c.Deref(ptr).attr`. Without this branch the attribute write path sees a *loaded struct value*
# as the base and fails with "base=非指针类型,无法做属性写入"; the read path silently does an
# extra struct load. Both must GEP the pointer itself (R4 migration of `c.Deref` -> `*`).
# `(*ptr).attr` is AST UnaryOp(OpKind.Deref). Without this branch the attribute write path
# sees a *loaded struct value* as the base; both paths must GEP the pointer itself.
if at_value.kind() == ast.ASTKind.UnaryOp:
uo_cd: ast.UnaryOp* = ast.UnaryOp*(at_value)
if uo_cd.op != ast.OpKind.Deref or uo_cd.operand is None: return None
inner_uo: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo_cd.operand, None, 0, trans)
if inner_uo is not None and self.is_ptr_type(inner_uo.Ty) != 0: return inner_uo
return None
if at_value.kind() != ast.ASTKind.Call: return None
cl_cd: ast.Call* = ast.Call*(at_value)
if cl_cd.func is None or cl_cd.func.kind() != ast.ASTKind.Attribute: return None
f_cd: ast.Attribute* = ast.Attribute*(cl_cd.func)
if f_cd.attr is None or string.strcmp(f_cd.attr, "Deref") != 0: return None
if f_cd.value is None or f_cd.value.kind() != ast.ASTKind.Name: return None
nm_cd: ast.Name* = ast.Name*(f_cd.value)
if nm_cd.id is None or string.strcmp(nm_cd.id, "c") != 0: return None
if cl_cd.args is None or len(cl_cd.args) < 1: return None
inner_cd: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cl_cd.args.get(0), None, 0, trans)
if inner_cd is not None and self.is_ptr_type(inner_cd.Ty) != 0: return inner_cd
return None
def translate_attribute(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Translate an attribute access and return the loaded field value."""
@@ -482,7 +485,7 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
return None # it is a submodule, hand it to the nested attribute path
# special case: t.ASM_DESCR is the inline-asm constraint namespace, not a CDefine; its
# .XXX is handled by _asm_resolve_constraint, so return None for the nested path to handle.
if string.strcmp(nm_ma.id, "t") == 0 and string.strcmp(at.attr, "ASM_DESCR") == 0: return None
if string.strcmp(nm_ma.id, "ASM_DESCR") == 0 and string.strcmp(at.attr, "ASM_DESCR") == 0: return None
# ALL_CAPS module attribute that is not a CDefine: load it as a cross-module global when the
# module defines a same-named global (e.g. PA_DEPS_MAX_STR); else fail-fast (undefined value).
if self.Gen.ExprGenCdef._is_cdefine_name_pattern(at.attr) != 0:
@@ -834,20 +837,25 @@ novirtual class ExprGenAttr(ExprGenBase.ExprGenBase):
if fe is None: return None
return self.Gen.StructGen.get_field_annot_class_name(fe)
# _base_class_from_cast_base - bare class name of a pointer-cast base `T*(x)` (parsed as `(T*)(x)`, whose
# union-with-t.CPtr type is i8*), extracted from the BinOp annotation; bare name matches TPC's convention.
# _base_class_from_cast_base - bare class name of a pointer-cast base `T*(x)` (parsed as CPtr[T]),
# extracted from the marker subscript; bare name matches TPC's convention.
def _base_class_from_cast_base(self, pool: memhub.MemBuddy*, trans: HT.Translator*, node: ast.AST*) -> str:
"""Return the bare class name of a T*(x) cast base, or None for other forms."""
if node is None or trans is None: return None
if node.kind() != ast.ASTKind.Call: return None
cl_cb: ast.Call* = ast.Call*(node)
if cl_cb.func is None or cl_cb.func.kind() != ast.ASTKind.BinOp: return None
bo_cb: ast.BinOp* = ast.BinOp*(cl_cb.func)
if bo_cb.op != ast.OpKind.BitOr: return None
# when one side of the union is the t.CPtr marker (the nested union of `T*` / `T**`), take the
# other side; otherwise (a hand-written `X | Y`) take the left side, matching parser _make_ptr_type
tgt_cb: ast.AST* = bo_cb.left
if self.Gen.TypeGenResolve._is_cptr_marker(bo_cb.left, trans._imported_modules) != 0: tgt_cb = bo_cb.right
tgt_cb: ast.AST* = node
# A cast call node: the pointer type is the callee.
if tgt_cb.kind() == ast.ASTKind.Call:
cl_cb: ast.Call* = ast.Call*(tgt_cb)
if cl_cb.func is None: return None
tgt_cb = cl_cb.func
# Unwrap every CPtr[...] layer; the innermost node is the cast base.
while tgt_cb is not None and tgt_cb.kind() == ast.ASTKind.Subscript:
sub_cb: ast.Subscript* = ast.Subscript*(tgt_cb)
if sub_cb.value is None or sub_cb.value.kind() != ast.ASTKind.Name: break
mk_nm: ast.Name* = ast.Name*(sub_cb.value)
if mk_nm.id is None: break
if string.strcmp(mk_nm.id, "CPtr") != 0 and string.strcmp(mk_nm.id, "rawptr") != 0: break
tgt_cb = sub_cb.slice
if tgt_cb is None: return None
k_cb: int = tgt_cb.kind()
if k_cb == ast.ASTKind.Attribute:
+3 -4
View File
@@ -328,12 +328,11 @@ novirtual class ExprGenCdef(ExprGenBase.ExprGenBase):
if match_ok == 0: continue
if pyi_buf[line_start + name_len] != ':': continue
# check the line contains "t.CDefine"
# scan the line for "t.CDefine"
# check the line contains the CDefine marker
has_cdefine: int = 0
ci: sizet = line_start + name_len + 1
while ci + 8 <= line_start + line_len:
if (pyi_buf[ci] == 't' and pyi_buf[ci + 1] == '.' and pyi_buf[ci + 2] == 'C' and pyi_buf[ci + 3] == 'D' and pyi_buf[ci + 4] == 'e' and pyi_buf[ci + 5] == 'f' and pyi_buf[ci + 6] == 'i' and pyi_buf[ci + 7] == 'n' and pyi_buf[ci + 8] == 'e'):
while ci + 7 <= line_start + line_len:
if (pyi_buf[ci] == 'C' and pyi_buf[ci + 1] == 'D' and pyi_buf[ci + 2] == 'e' and pyi_buf[ci + 3] == 'f' and pyi_buf[ci + 4] == 'i' and pyi_buf[ci + 5] == 'n' and pyi_buf[ci + 6] == 'e'):
has_cdefine = 1
break
ci += 1
+10
View File
@@ -344,6 +344,11 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None: return seg
# On a find_struct miss, retry with the last '.'-prefix (module
# qualifier) stripped: memhub.MemBuddy -> MemBuddy.
@@ -363,6 +368,11 @@ novirtual class ExprGenCls(ExprGenBase.ExprGenBase):
if seglen > 0 and seglen < 256:
string.strncpy(seg, annot + s, seglen)
seg[seglen] = '\0'
# Unwrap the CPtr[T] pointer marker: the class name is inside.
while seglen > 6 and string.strncmp(seg, "CPtr[", 5) == 0 and seg[seglen - 1] == ']':
seg = seg + 5
seglen -= 6
seg[seglen] = '\0'
if self.Gen.StructGen.find_struct(seg) is not None: return seg
# On a find_struct miss, retry with the last '.'-prefix (module
# qualifier) stripped: memhub.MemBuddy -> MemBuddy.
+4 -2
View File
@@ -368,9 +368,11 @@ novirtual class ExprGenName(ExprGenBase.ExprGenBase):
if elem_buf_e is None: return None
string.strncpy(elem_buf_e, lb_e + 1, elen_e)
elem_buf_e[elen_e] = '\0'
# Strip the rawptr[...] wrapper (elements declared as object pointers, e.g. rawptr[sha1.AST])
# Strip the pointer wrapper (elements declared as object pointers): CPtr[...] / rawptr[...]
cur_e: str = elem_buf_e
if string.strncmp(cur_e, "rawptr[", 7) == 0: cur_e = cur_e + 7
while string.strncmp(cur_e, "CPtr[", 5) == 0 or string.strncmp(cur_e, "rawptr[", 7) == 0:
if string.strncmp(cur_e, "CPtr[", 5) == 0: cur_e = cur_e + 5
else: cur_e = cur_e + 7
# Split unions by '|', trimming leading/trailing whitespace, trailing ']' and quotes; each
# segment goes to find_struct (builtin names return None; "sha1.Class" prefix matches by module).
total_e: sizet = string.strlen(cur_e)
+2 -14
View File
@@ -201,13 +201,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
k: int = node.kind()
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
mod_nm: ast.Name* = ast.Name*(at.value)
if mod_nm.id is not None and string.strcmp(mod_nm.id, "t") == 0 and at.attr is not None:
if self._type_name_unsigned(at.attr) != 0: return 1
return 0
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute: return 0
# Plain function call: widen by the callee's **declared return type**. Without this,
# `uint64(f())` with `def f() -> uint32` emitted `sext i32 -> i64` (verified in the
# generated IR), so a u32 with bit 31 set grew 0xFFFFFFFF in the high half — a real
@@ -359,13 +353,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
k: int = node.kind()
if k == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(node)
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
mod_nm: ast.Name* = ast.Name*(at.value)
if mod_nm.id is not None and string.strcmp(mod_nm.id, "t") == 0 and at.attr is not None:
if self._spec_sign_from_annot(at.attr) == TSpec.TS_SIGN_SIGNED: return 1
return 0
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute: return 0
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None:
+13 -24
View File
@@ -42,23 +42,12 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase):
call_node: ast.Call* = ast.Call*(deco)
if call_node.func is None: continue
cfk: int = call_node.func.kind()
if cfk != ast.ASTKind.Attribute: continue
# Detect c.Attribute
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
if func_attr.attr is None: continue
if string.strcmp(func_attr.attr, "Attribute") != 0: continue
if func_attr.value is None: continue
fvk: int = func_attr.value.kind()
if fvk != ast.ASTKind.Name: continue
c_name: ast.Name* = ast.Name*(func_attr.value)
if c_name.id is None: continue
if string.strcmp(c_name.id, "c") != 0: continue
# 不要求 `import c`:`@__attribute__(...)` 会被 ast/parser.vp(_DesugarMagicAttribute)
# 脱糖成 `@c.Attribute(...)`,这里的 `c.` 前缀是编译器自己产生的(用户写的是
# `@__attribute__`,并非 `@c.Attribute`)。若在此要求 `import c`,用户明明没引入 c
# 就会把整条装饰器静默丢弃(`stackrealign`/`always_inline` 等函数属性全失效)。
# extract_func_section 本就没有这个守卫,二者保持一致。
# Detect the builtin Attribute(...) wrapper emitted by @__attribute__(...).
if cfk != ast.ASTKind.Name: continue
func_nm: ast.Name* = ast.Name*(call_node.func)
if func_nm.id is None: continue
if string.strcmp(func_nm.id, "Attribute") != 0: continue
# Iterate args and extract attrs
if call_node.args is None: continue
@@ -80,7 +69,7 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase):
if found_any == 0: return None
return attrs_buf
def extract_func_section(self, pool: memhub.MemBuddy*, decorator_list: list[ast.AST*]*, imported_modules: str) -> str:
"""Extract @c.Attribute(t.attr.section(...)) as the attr-group text."""
"""Extract @Attribute(attr.section(...)) as the attr-group text."""
if pool is None or decorator_list is None: return None
dn: sizet = len(decorator_list)
if dn == 0: return None
@@ -88,9 +77,9 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase):
deco: ast.AST* = decorator_list.get(di)
if deco is None or deco.kind() != ast.ASTKind.Call: continue
call_node: ast.Call* = ast.Call*(deco)
if call_node.func is None or call_node.func.kind() != ast.ASTKind.Attribute: continue
func_attr: ast.Attribute* = ast.Attribute*(call_node.func)
if func_attr.attr is None or string.strcmp(func_attr.attr, "Attribute") != 0: continue
if call_node.func is None or call_node.func.kind() != ast.ASTKind.Name: continue
func_nm: ast.Name* = ast.Name*(call_node.func)
if func_nm.id is None or string.strcmp(func_nm.id, "Attribute") != 0: continue
if call_node.args is None: continue
args_list: list[ast.AST*]* = call_node.args
an: sizet = len(args_list)
@@ -272,11 +261,11 @@ novirtual class FuncGenBase(CallGenBase.CallGenBase):
for ai in range(int(an)):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is not None and arg.arg is not None:
# t.CVoid means no args: skip
# CVoid / void means no args: skip
if arg.annotation is not None:
if arg.annotation.kind() == ast.ASTKind.Attribute:
at_cf: ast.Attribute* = ast.Attribute*(arg.annotation)
if at_cf.attr is not None and string.strcmp(at_cf.attr, "CVoid") == 0: continue
if arg.annotation.kind() == ast.ASTKind.Name:
at_cn: ast.Name* = ast.Name*(arg.annotation)
if at_cn.id is not None and (string.strcmp(at_cn.id, "CVoid") == 0 or string.strcmp(at_cn.id, "void") == 0): continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, None, None, None, 0)
+14 -20
View File
@@ -38,7 +38,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
arg: ast.Arg* = ast.Arg*(alist.get(i))
if arg is None or arg.arg is None: continue
# CVoid params are not emitted into the signature, so they must not occupy a mode slot.
if arg.annotation is not None and self.Gen.TypeGenResolve._is_t_attr(arg.annotation, "CVoid", imp) != 0: continue
if arg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imp) != 0: continue
m: int = 0
if arg.annotation is not None: m = self.Gen.TypeGenResolve.ref_param_mode(arg.annotation, imp)
if m == 1: buf[cnt] = char(49)
@@ -110,14 +110,11 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
# A lone t.CExport return annotation is an export modifier with no real type -> Void
# A lone CExport return annotation is an export modifier with no real type -> Void
# (CExport fell back to i32, forcing the tail check to return i32: mm.vp free root cause).
if fd.returns.kind() == ast.ASTKind.Attribute:
ce_at: ast.Attribute* = ast.Attribute*(fd.returns)
if ce_at.attr is not None and string.strcmp(ce_at.attr, "CExport") == 0:
if ce_at.value is not None and ce_at.value.kind() == ast.ASTKind.Name:
ce_nm: ast.Name* = ast.Name*(ce_at.value)
if ce_nm.id is not None and string.strcmp(ce_nm.id, "t") == 0: ret_ty = mlirlite.Void(pool)
if fd.returns.kind() == ast.ASTKind.Name:
ce_at: ast.Name* = ast.Name*(fd.returns)
if ce_at.id is not None and string.strcmp(ce_at.id, "CExport") == 0: ret_ty = mlirlite.Void(pool)
if ret_ty is None and fd.returns is not None:
if self.Gen.TypeGenErr.has_decorator_marker(fd.returns, "State", 0) != 0: ret_ty = mlirlite.Void(pool)
# @c.CReturn decorator removed: encountering it is a compile-time error (use -> tuple[...] for multiple returns)
@@ -206,7 +203,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if arg is None or arg.arg is None: continue
# CVoid means empty param: skip
if arg.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg.annotation, "CVoid", imported_modules) != 0: continue
if self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
@@ -280,14 +277,11 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
# Value-reference markers are parameter-only: `-> &T` fails fast (never a silent Ptr(T) return).
self.Gen.TypeGenResolve.reject_ref_marker_position(fd.returns, imported_modules, "a function return type")
ret_ty = self.Gen.TypeGenResolve.resolve_annotation_type(pool, fd.returns, imported_modules, from_imports, trans, 0)
# A lone t.CExport return annotation is an export modifier with no real type -> Void
# A lone CExport return annotation is an export modifier with no real type -> Void
# (CExport fell back to i32, forcing return i32: mm.vp free root cause). Mixed types keep the resolved result.
if fd.returns.kind() == ast.ASTKind.Attribute:
ce_at2: ast.Attribute* = ast.Attribute*(fd.returns)
if ce_at2.attr is not None and string.strcmp(ce_at2.attr, "CExport") == 0:
if ce_at2.value is not None and ce_at2.value.kind() == ast.ASTKind.Name:
ce_nm2: ast.Name* = ast.Name*(ce_at2.value)
if ce_nm2.id is not None and string.strcmp(ce_nm2.id, "t") == 0: ret_ty = mlirlite.Void(pool)
if fd.returns.kind() == ast.ASTKind.Name:
ce_at2: ast.Name* = ast.Name*(fd.returns)
if ce_at2.id is not None and string.strcmp(ce_at2.id, "CExport") == 0: ret_ty = mlirlite.Void(pool)
# If the return annotation is purely a decorator marker (e.g. t.State, no real type), use void.
# Must be checked before infer_return_type, which would return at least i32.
@@ -412,7 +406,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
arg_e: ast.Arg* = ast.Arg*(alist_e.get(ai_e))
if arg_e is not None and arg_e.arg is not None:
# CVoid means empty param: skip, do not add to the function signature
if arg_e.annotation is not None and self.Gen.TypeGenResolve._is_t_attr(arg_e.annotation, "CVoid", imported_modules) != 0: continue
if arg_e.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg_e.annotation, "CVoid", imported_modules) != 0: continue
param_ty_e: mlirlite.LLVMType* = i32_ty
if arg_e.annotation is not None:
resolved_e: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg_e.annotation, imported_modules, from_imports, trans, 0)
@@ -479,7 +473,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
rearg: ast.Arg* = ast.Arg*(reuse_list.get(reuse_i))
if rearg is None or rearg.arg is None: continue
# CVoid means empty param: not part of the signature (mirrors the add_param loops above)
if rearg.annotation is not None and self.Gen.TypeGenResolve._is_t_attr(rearg.annotation, "CVoid", imported_modules) != 0: continue
if rearg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(rearg.annotation, "CVoid", imported_modules) != 0: continue
if reused_p is None:
# 复用声明形参少于定义:签名与函数体必然错配,禁止静默,fail-fast
self.Gen.TypeGenErr.fatal_error(rearg, f"reused declaration for '{fd.name}' has fewer parameters than the definition", "TypeError")
@@ -537,7 +531,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
arg: ast.Arg* = ast.Arg*(alist.get(ai))
if arg is not None and arg.arg is not None:
# CVoid means empty param: skip, do not add to the function signature
if arg.annotation is not None and self.Gen.TypeGenResolve._is_t_attr(arg.annotation, "CVoid", imported_modules) != 0: continue
if arg.annotation is not None and self.Gen.TypeGenResolve._is_builtin_marker(arg.annotation, "CVoid", imported_modules) != 0: continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
resolved: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg.annotation, imported_modules, from_imports, trans, 0)
@@ -678,7 +672,7 @@ novirtual class FuncGenDef(CallGenBase.CallGenBase):
if arg2 is not None and arg2.arg is not None:
# CVoid means empty param: skip, do not create an alloca
if arg2.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg2.annotation, "CVoid", imported_modules) != 0: continue
if self.Gen.TypeGenResolve._is_builtin_marker(arg2.annotation, "CVoid", imported_modules) != 0: continue
param_ty2: mlirlite.LLVMType* = i32_ty
if arg2.annotation is not None:
resolved2: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg2.annotation, imported_modules, from_imports, trans, 0)
+2 -2
View File
@@ -94,7 +94,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
arg_n: ast.Arg* = ast.Arg*(alist_n.get(ai_n))
if arg_n is None or arg_n.arg is None: continue
if arg_n.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg_n.annotation, "CVoid", imported_modules) != 0: continue
if self.Gen.TypeGenResolve._is_builtin_marker(arg_n.annotation, "CVoid", imported_modules) != 0: continue
pty_n: mlirlite.LLVMType* = i32_ty
if arg_n.annotation is not None:
resolved_n: mlirlite.LLVMType* = self.Gen.TypeGenResolve.resolve_annotation_type(pool, arg_n.annotation, imported_modules, from_imports, trans, 0)
@@ -147,7 +147,7 @@ novirtual class FuncGenMixin(CallGenBase.CallGenBase):
arg_p: ast.Arg* = ast.Arg*(alist_p.get(api))
if arg_p is None or arg_p.arg is None: continue
if arg_p.annotation is not None:
if self.Gen.TypeGenResolve._is_t_attr(arg_p.annotation, "CVoid", imported_modules) != 0: continue
if self.Gen.TypeGenResolve._is_builtin_marker(arg_p.annotation, "CVoid", imported_modules) != 0: continue
if idx_p >= param_count: break
pty2_n: mlirlite.LLVMType* = i32_ty
if param_tys is not None:
+5 -5
View File
@@ -86,16 +86,16 @@ def fatal_error(node: ast.AST*, msg: str, err_type: str = "SyntaxError") -> None
if gg is not None: gg.TypeGenErr.fatal_error(node, msg, err_type)
# ---------------- map_t_type (delegates to TypeGenResolve.map_t_type) ----------------
def map_t_type(pool: memhub.MemBuddy*, type_name: str) -> mlirlite.LLVMType*:
"""Delegate to gen.map_t_type"""
if _g_gen is not None: _g_gen.TypeGenResolve.map_t_type(pool, type_name)
# ---------------- map_builtin_type (delegates to TypeGenResolve.map_builtin_type) ----------------
def map_builtin_type(pool: memhub.MemBuddy*, type_name: str) -> mlirlite.LLVMType*:
"""Delegate to gen.map_builtin_type"""
if _g_gen is not None: _g_gen.TypeGenResolve.map_builtin_type(pool, type_name)
return None
# ---------------- resolve_typedef_or_map (delegates to TypeGenResolve.resolve_typedef_or_map) ----------------
def resolve_typedef_or_map(pool: memhub.MemBuddy*, name: str, imported_modules: str, from_imports: str, trans: HT.Translator* = None) -> mlirlite.LLVMType*:
"""Delegate to gen.resolve_typedef_or_map: CTypedef table first, then map_t_type."""
"""Delegate to gen.resolve_typedef_or_map: CTypedef table first, then map_builtin_type."""
if _g_gen is not None: return _g_gen.TypeGenResolve.resolve_typedef_or_map(pool, name, imported_modules, from_imports, trans)
return None
+6 -3
View File
@@ -546,9 +546,12 @@ novirtual class ImportsGenMixin:
def _check_import_sha1(self, node: ast.AST*, mod_name: str) -> int:
"""Fail-fast when an imported module's SHA1 is not registered."""
if mod_name is None: return 1
# t/c are internal libraries exempt from includes scanning; so are ctraits (a .vpi
# compile-time macro lib) and viperbuild (a compiler builtin macro lib).
if string.strcmp(mod_name, "t") == 0 or string.strcmp(mod_name, "c") == 0 or string.strcmp(mod_name, "ctraits") == 0: return 1
# t / c were retired: their types are builtin and their C helpers now have language syntax.
if string.strcmp(mod_name, "t") == 0 or string.strcmp(mod_name, "c") == 0:
self._report_import_error(node, "`import t` / `import c` is retired; builtin types and C helpers no longer live in modules")
return 0
# ctraits (a .vpi compile-time macro lib) and viperbuild (a compiler builtin macro lib) stay exempt.
if string.strcmp(mod_name, "ctraits") == 0: return 1
if string.strcmp(mod_name, "viperbuild") == 0: return 1
mod_sha1: str = StubMergerStore.LookupSha1ByModName(mod_name)
if mod_sha1 is None:
+1 -8
View File
@@ -53,18 +53,11 @@ novirtual class StmtGenCifMixin:
return 0
def _is_cif_call(self, node: ast.AST*) -> int:
"""Check whether test is a cif marker call (c.@CIf family, or a bare @CIf-family name)."""
"""Check whether test is a cif marker call (bare @CIf-family name)."""
if node is None or node.kind() != ast.ASTKind.Call: return 0
cl: ast.Call* = ast.Call*(node)
if cl.func is None: return 0
fk: int = cl.func.kind()
if fk == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
if at.attr is None: return 0
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
vn: ast.Name* = ast.Name*(at.value)
if vn.id is not None and string.strcmp(vn.id, "c") == 0: return self._is_cif_attr(at.attr)
return 0
if fk == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cl.func)
if nm.id is not None and self._is_cif_attr(nm.id) != 0: return 1
+2 -2
View File
@@ -2652,7 +2652,7 @@ novirtual class StructGenMixin:
if mod_buf_f is not None:
string.strncpy(mod_buf_f, name, mod_len_f)
mod_buf_f[mod_len_f] = '\0'
if string.strcmp(mod_buf_f, "t") != 0 and string.strcmp(mod_buf_f, "c") != 0:
if mod_buf_f is not None:
# A 16-hex module segment is a SHA1 literal (e.g. "{9aab}.Scene3D", TypeGenResolve L1397-1402's sha1-prefix form): skip the store module-name lookup (store keys are module names, not sha1) and match name+sha1 exactly.
# Otherwise _resolve_module_sha1("9aab") returns None and a bare-name fallback returns the first-registered wrong cross-module Scene3D entry (VKernel gfx / App gfx both define Scene3D -> wrong ownership -> ret_sha1=0ae -> Phase A _deps_pending deadlock). [C10 convergence] The module segment is exactly 16 lowercase hex = SHA1 literal: go through GenApi.is_sha1_hex16 (single source of truth).
mod_hex_ok: int = GenApi.is_sha1_hex16(mod_buf_f)
@@ -2905,7 +2905,7 @@ novirtual class StructGenMixin:
if mod_buf_q is not None:
string.strncpy(mod_buf_q, class_name, mod_len_q)
mod_buf_q[mod_len_q] = '\0'
if string.strcmp(mod_buf_q, "t") != 0 and string.strcmp(mod_buf_q, "c") != 0:
if mod_buf_q is not None:
# A 16-hex module segment ("{16hex}.Class", the normalized generic type-arg form) is the module SHA1; a registered class proves that module compiled, so match name+sha1 exactly without _resolve_module_sha1 (store keys are module names, so querying the sha1 is always None and would fail-fast wrongly).
# [C10 convergence] The module segment is exactly 16 lowercase hex = SHA1 literal: go through GenApi.is_sha1_hex16; generic subscript dots are already skipped by _last_dot_outside_brackets, so only top-level a.b forms remain here.
mod_hex_ok_q: int = GenApi.is_sha1_hex16(mod_buf_q)
+11 -16
View File
@@ -425,19 +425,17 @@ novirtual class TranslatorGenMixin:
viperlib.snprintf(buf, body_len + 8, "{ %s }", body)
return buf
# Module-level global t.attr.* extraction: attributes live in the annotation union
# (`T | t.attr.section(".x")`) and are written directly to gv.Section/Align/Linkage fields.
# Module-level global attr.* extraction: attributes are marker wrappers
# (`attr.section(".x")[T]`) written directly to gv.Section/Align/Linkage fields.
def _scan_global_tattr(self, node: ast.AST*, want_name: str) -> ast.AST*:
"""Find the t.attr.<want_name> node in a global type annotation; None when absent."""
"""Find the attr.<want_name> marker in a global type annotation; None when absent."""
if node is None: return None
if node.kind() == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if bop.op == ast.OpKind.BitOr:
hit_l: ast.AST* = self._scan_global_tattr(bop.left, want_name)
if hit_l is not None: return hit_l
return self._scan_global_tattr(bop.right, want_name)
return None
if self.Gen.ClassGenBase._tattr_name(node, want_name) != 0: return node
if node.kind() == ast.ASTKind.Subscript:
sub_t: ast.Subscript* = ast.Subscript*(node)
hit_s: ast.AST* = self._scan_global_tattr(sub_t.value, want_name)
if hit_s is not None: return hit_s
return self._scan_global_tattr(sub_t.slice, want_name)
return None
def _extract_global_attr_section(self, node: ast.AST*, var_name: str) -> str:
"""Extract the section name from `T | t.attr.section("xxx")`; fail-fast when empty."""
@@ -960,12 +958,9 @@ novirtual class TranslatorGenMixin:
if cls_nm_ml is not None: self.Gen.VarGen.set_var_annot_class_name(trans.SymTab, var_name, cls_nm_ml)
# Module-level str*/bytes* globals: mark IsPtrElement so var[i] steps by 8-byte
# pointers; otherwise it degrades to a 1-byte GEP + load i8 that truncates pointers.
if aa_ml.annotation.kind() == ast.ASTKind.BinOp:
bop_ml: ast.BinOp* = ast.BinOp*(aa_ml.annotation)
if bop_ml.op == ast.OpKind.BitOr:
if self.Gen.AssignGen._annotation_has_cptr_marker(aa_ml.annotation) != 0:
if self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "str") != 0 or self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "bytes") != 0:
self.Gen.VarGen.set_var_ptr_element(trans.SymTab, var_name)
if self.Gen.AssignGen._annotation_has_cptr_marker(aa_ml.annotation) != 0:
if self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "str") != 0 or self.Gen.AssignGen._annotation_contains_name(aa_ml.annotation, "bytes") != 0:
self.Gen.VarGen.set_var_ptr_element(trans.SymTab, var_name)
return 1
return 0
+8 -20
View File
@@ -351,29 +351,17 @@ class TypeGenCType(TypeGenBase.TypeGenBase):
vn: sizet = len(self.CTypedefVisiting)
if vn > 0: self.CTypedefVisiting.pop()
def _is_ctypedef_annotation(self, annotation: ast.AST*) -> int:
"""Return 1 if the annotation is t.CTypedef, otherwise 0."""
"""Return 1 if the annotation is the CTypedef marker, otherwise 0."""
if annotation is None: return 0
k_v: int = annotation.kind()
if k_v != ast.ASTKind.Attribute: return 0
at: ast.Attribute* = ast.Attribute*(annotation)
# Use ast module accessors for field access: a direct at.attr read may emit a
# wrong GEP from the stub type and crash strcmp.
at_attr_v: str = ast.attribute_get_attr(at)
if at_attr_v is None: return 0
if annotation.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(annotation)
nm_id_v: str = ast.name_get_id(nm)
if nm_id_v is None: return 0
# Length-bounded compare instead of strcmp: the token may lack a NUL and strcmp
# reads past the page; check byte 9 is a terminator/separator.
if string.strncmp(at_attr_v, "CTypedef", 8) != 0: return 0
sep_ch_d: char = at_attr_v[8]
# reads past the page; check byte 8 is a terminator/separator.
if string.strncmp(nm_id_v, "CTypedef", 8) != 0: return 0
sep_ch_d: char = nm_id_v[8]
if sep_ch_d != '\0' and sep_ch_d != ' ' and sep_ch_d != '\n' and sep_ch_d != '\r' and sep_ch_d != '\t' and sep_ch_d != '|': return 0
at_val_v: ast.AST* = ast.attribute_get_value(at)
if at_val_v is None or at_val_v.kind() != ast.ASTKind.Name: return 0
mod_nm: ast.Name* = ast.Name*(at_val_v)
mod_id_v: str = ast.name_get_id(mod_nm)
if mod_id_v is None: return 0
# same length-bounded compare ("t" may lack a NUL)
if string.strncmp(mod_id_v, "t", 1) != 0: return 0
sep_ch_m: char = mod_id_v[1]
if sep_ch_m != '\0' and sep_ch_m != ' ' and sep_ch_m != '\n' and sep_ch_m != '\r' and sep_ch_m != '\t' and sep_ch_m != '|': return 0
return 1
def ScanCTypedefDeclarations(self, pool: memhub.MemBuddy*, tree: ast.AST*) -> int:
"""Scan top-level AST nodes and register all NAME: t.CTypedef = Type declarations."""
+16 -12
View File
@@ -140,18 +140,21 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
k: int = node.kind()
# Attribute node: t.CExtern, t.State, t.CExport, t.CInline
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(node)
if at.attr is not None:
if string.strcmp(at.attr, marker_name) == 0: return 1
# Name node: a bare builtin marker (CExtern / State / CExport / CInline / CStatic / CConst ...)
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(node)
if nm.id is not None and string.strcmp(nm.id, marker_name) == 0: return 1
return 0
# BinOp node: int | t.CExtern (recurse into both children)
if k == ast.ASTKind.BinOp:
bop: ast.BinOp* = ast.BinOp*(node)
if self.has_decorator_marker(bop.left, marker_name, depth + 1) != 0: return 1
if self.has_decorator_marker(bop.right, marker_name, depth + 1) != 0: return 1
# Subscript node: a marker wrapper `CConst[T]` / descriptor chain; the marker may be on this
# level or nested inside the wrapped type.
if k == ast.ASTKind.Subscript:
sub_m: ast.Subscript* = ast.Subscript*(node)
if sub_m.value is not None and sub_m.value.kind() == ast.ASTKind.Name:
mv: ast.Name* = ast.Name*(sub_m.value)
if mv.id is not None and string.strcmp(mv.id, marker_name) == 0: return 1
if sub_m.slice is not None:
return self.has_decorator_marker(sub_m.slice, marker_name, depth + 1)
return 0
return 0
@@ -162,7 +165,8 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
sub: ast.Subscript* = ast.Subscript*(node)
if sub.value is None or sub.value.kind() != ast.ASTKind.Name: return 0
rp_nm: ast.Name* = ast.Name*(sub.value)
if rp_nm.id is None or string.strcmp(rp_nm.id, "rawptr") != 0: return 0
if rp_nm.id is None: return 0
if string.strcmp(rp_nm.id, "rawptr") != 0 and string.strcmp(rp_nm.id, "CPtr") != 0: return 0
if sub.slice is None: return 0
return self.has_decorator_marker(sub.slice, "CVolatile", 0)
@@ -298,7 +302,7 @@ novirtual class TypeGenErr(TypeGenBase.TypeGenBase):
if arg is None or arg.arg is None: continue
param_ty: mlirlite.LLVMType* = i32_ty
if arg.annotation is not None:
# Unified: prefer the CTypedef dynamic table, fall back to map_t_type
# Unified: prefer the CTypedef dynamic table, fall back to map_builtin_type
# (stage 2 removed hardcoded aliases; BYTE etc. now rely on library typedefs)
if arg.annotation.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(arg.annotation)
File diff suppressed because it is too large. Load diff
+38
View File
@@ -86,6 +86,44 @@ def SpecSignFromTypeNameSpan(text: str, start: sizet, end: sizet) -> int:
if _spec_name_eq(text, start, end, "CSizeT") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "CUIntPtrT") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "CBool") != 0: return TS_SIGN_UNSIGNED
# --- builtin short names ---
if _spec_name_eq(text, start, end, "sizet") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "size_t") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uint8") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uint16") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uint32") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uint64") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintfast8") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintfast16") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintfast32") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintfast64") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintleast8") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintleast16") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintleast32") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintleast64") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uchar") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "ushort") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uint") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "ulong") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "ulonglong") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "uintptr") != 0: return TS_SIGN_UNSIGNED
if _spec_name_eq(text, start, end, "int8") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "int16") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "int32") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "int64") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intfast8") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intfast16") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intfast32") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intfast64") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intleast8") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intleast16") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intleast32") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intleast64") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "short") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "long") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "longlong") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "ptrdifft") != 0: return TS_SIGN_SIGNED
if _spec_name_eq(text, start, end, "intptr") != 0: return TS_SIGN_SIGNED
# --- builtin lowercase scalars ---
if _spec_name_eq(text, start, end, "bool") != 0: return TS_SIGN_UNSIGNED
# --- t/c core signed primitives ---
+4 -217
View File
@@ -1,223 +1,10 @@
# ViperC
[English](#english) | [中文](#中文)
The **Viper language compiler, written in Viper itself** — and it is **self-hosted**: ViperC compiles its own source and produces `viperc.exe`. It translates Viper sources to LLVM IR, then drives `llc` + `clang++` to link a native Windows executable. The first-generation CPython compiler ([TransPyC](https://git.gvsds.com/GVSDS/TransPyC)) is now only kept as the bootstrapping **seed**.
**用 Viper 语言编写的 Viper 编译器**,且**已完成自举**:ViperC 能编译自身源码并产出 `viperc.exe`。它把 Viper 源码翻译为 LLVM IR,再调用 `llc` + `clang++` 链接为 Windows 原生可执行文件。第一代 CPython 编译器([TransPyC](https://git.gvsds.com/GVSDS/TransPyC))现在只作为自举**种子**保留。
This is a request to remove the Chinese portions from the ViperC README. Here is the resulting document with all Chinese sections stripped, keeping only the English content.
---
<a id="中文"></a>
# ViperC
## 中文
### 这是什么
`ViperC` 是 Viper 的第二代编译器:**用 Viper 自己写、也被 Viper 自己编译**。整条链路上没有任何 Python 运行时参与编译 —— 词法/语法分析走 `includes/ast`(自研解析器,9 个文件),IR 生成走 `App/lib/core/Gen/`,目标代码由 `llc` 与 `clang++` 产出。
自举链路:
```text
TransPyC(种子,CPython 实现)
│ 只做一次:把 ViperC 源码编译成 viperc.exe
▼
viperc.exe(第一阶段产物)
│ 自举:用这个 exe 再编译 ViperC 自身
▼
viperc.exe(自举产物,当前仓库随包/随 exe\ 提供的那份)
│
└─ 用它编译任意 Viper 工程(ViperApps\*、Test\* …)
```
现状与规模(本机实测):
| 项 | 值 |
| --- | --- |
| 编译器源码 | `App/` 下 **82 个 `.vp` 文件,63,783 行** |
| 其中翻译器核心 | `App/lib/core/Gen/` **54 个文件,47,297 行** |
| 阶段与合并 | `App/lib/Phase/` 6 文件 5,339 行;`App/lib/Stub/` 9 文件 6,207 行 |
| 工程与工具 | `App/lib/Projectrans/` 3 文件 712 行 |
| 自测/回归套件 | 工作区根 `Test\`(**独立仓库**):**97 个工程,129 个 `.vp`**,由 `Test\SequentialRegressionTest.py` 驱动 |
| 平台 | Windows x64(源码直接 `import win32.*`) |
> 自举可行性分析与历史数据见 [`wiki/13-bootstrapping.md`](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/13-bootstrapping.md)(该文标注:Phase A → Phase B/C 已跑通,编译器已脱离 Python 运行时)。
### 目录结构
```text
ViperC/
├── project.vpj # 工程配置(source_dir=./App, build_dir=./__vpcbuild__, 输出 viperc.exe)
├── App/ # 编译器源码(Viper)
│ ├── main.vp # CLI 入口:内存池初始化、单例注入、参数解析、Phase 调度
│ └── lib/
│ ├── Projectrans/
│ │ ├── Config.vp # project.vpj 加载与路径解析(含 includes 缓存目录)
│ │ └── Utils.vp # SHA1 / 目录清理 / 杂项
│ ├── core/
│ │ ├── VLogger.vp # 日志(info/warn/error/banner/success)
│ │ ├── IncludesScanner.vp # includes 目录扫描(决定哪些需重编译)
│ │ ├── CompilerFactory.vp # 工厂:持有 mb + perm_pool 两个 MemBuddy 单例
│ │ └── Gen/ # 翻译器(52 文件)—— 把 AST 变为 LLVM IR
│ │ ├── Generator.vp / Translator.vp / GenApi.vp / GenTypes.vp
│ │ ├── BaseGenMixin / AssignGenMixin / VarGenMixin / ImportsGenMixin
│ │ ├── MatchGenMixin / AsyncGenMixin / StructGenMixin / TranslatorGenMixin
│ │ ├── Call/ (12) 调用生成:普通调用 / C 内建 / 构造 / 虚表 / 变参 / 内联汇编 …
│ │ ├── Class/ (6) 类:定义 / 泛型 / 虚表 / 枚举 / Mixin
│ │ ├── Expr/ (9) 表达式:名字 / 常量 / 运算 / 属性 / f-string / CDef …
│ │ ├── Func/ (3) 函数定义
│ │ ├── Stmt/ (3) 语句:声明 / C-if / Mixin
│ │ └── Type/ (6) 类型解析与 C 类型映射
│ ├── Phase/
│ │ ├── BuildPipeline.vp # .ll → llc → .obj → clang++ → .exe(含 ASan 插桩分支)
│ │ ├── Phase1/ # Phase 1:按需翻译 includes + stub 分离(.stub.ll / .text.ll)
│ │ └── Phase2/ # Phase 2:多文件工程翻译、IR 组合、编译
│ └── Stub/
│ ├── StubGen/ # .vpi 读取 + 声明转换
│ └── StubMerger/ # 多份 .stub.ll / .text.ll 依赖排序与合并
```
> 自测/回归套件不在本仓库:位于工作区根 `Test\`(独立仓库,97 个工程 129 个 `.vp`),
> 由 `Test\SequentialRegressionTest.py` 驱动(`--exe exe\viperc.exe`)。
### 前提条件
| 工具 | 说明 |
| --- | --- |
| Windows 10/11 x64 | 编译器源码直接 `import win32.*`,当前仅 Windows |
| `llc` | LLVM 静态编译器(本机 22.1.8),由 `project.vpj` 的 `compiler.cmd` 指定 |
| `clang++` | mingw-w64 工具链,`-fuse-ld=lld` 链接(本机 `D:/msys64/mingw64/bin/clang++.exe`) |
| ≥ 1.5 GiB 空闲内存 | 启动即 `malloc` 1 GiB 主池 + 512 MiB 常驻池 |
| [TransPyC](https://git.gvsds.com/GVSDS/TransPyC) | **仅当需要重建自举种子时**才需要 |
目录布局(编译时需要能找到 `includes/`,`project.vpj` 里写的是相对路径 `../includes`):
```text
TransPyC\ # 工作区根
├── TransPyC\ # 第一代编译器(种子)
├── ViperC\ # 本仓库
├── Test\ # 回归套件(独立仓库)
├── exe\ # 随包提供的自举产物(viperc.exe)
└── includes\ # Viper 标准库 + includes/ast 解析器
```
### 快速开始
```bat
:: 日常:直接用自举产物编译任意工程
exe\viperc.exe --project ViperApps\ViperMNIST\project.vpj --clean
:: 编译并立即运行
exe\viperc.exe --project ViperApps\ViperMNIST\project.vpj --clean --run
:: 只有在需要重建编译器本体(自举种子)时,才动用 TransPyC
python TransPyC\Projectrans.py --project ViperC\project.vpj --clean
```
产物位置 = `<工程目录>\<build_dir>\output\<linker.output>`,即默认 `<工程>\__vpcbuild__\output\app.exe`;
ViperC 自身因 `linker.output` 为 `viperc.exe`,产物是 `ViperC\__vpcbuild__\output\viperc.exe`。
跑回归套件(工作区根 `Test\`,独立仓库):
```bat
python Test\SequentialRegressionTest.py --exe exe\viperc.exe
```
### 命令行参数
| 参数 | 含义 |
| --- | --- |
| `--project <path>` | `project.vpj` 路径(不给则在当前目录、上级目录找) |
| `--src <dir>` | 单文件模式源目录(覆盖配置;入口取 `<dir>/main.vp`) |
| `--temp <dir>` | 声明接口临时目录(覆盖配置,默认 `<build_dir>/temp`) |
| `--output <dir>` | 输出目录(覆盖配置,默认 `<build_dir>/output`) |
| `--phase 1\|2\|all` | 1=只做 stub 分离;2=只做 IR 组合+编译;all=全部(默认) |
| `--cc <cmd>` | LLVM 编译器命令(覆盖配置) |
| `--build-dir <dir>` | 覆盖 `project.vpj` 的 `build_dir`(相对路径仍基于工程目录解析) |
| `--clean` | 清理 temp / output / includes 预编译缓存 |
| `--rebuild-includes` | 只删 includes 预编译缓存(`includes.binary`),强制重编 includes |
| `--clear-cache` | 只清 temp 缓存(`.vpi` / `.stub.ll` / `.text.ll`),强制重生成 stub |
| `--run` | 编译成功后立即执行产物 |
| `--repl` | 交互式 REPL(M1:表达式片段 → DLL → 调用回显);未给 `--project` 时按 cwd → `viperc.exe` 同目录查找 `repl.vpj` 模板工程(找不到则报错并提示 `--project`);与 `--asan` / `--phase2command` 互斥 |
| `--asan` | ASan 模式:先 `opt -passes=asan` 插桩;运行库须由 `project.vpj` 的 `linker.static_libs` 声明,否则 fail-fast |
| `--phase2command <bat>` | 不执行 `llc`/链接,只把命令写进指定 `.bat` |
| `--nowarn-register` | 抑制 Phase A 依赖类 WARN(跨模块 `text.ll` 未生成,下遍收敛) |
| `--no-llvmpeephole` | 关闭 FunctionPrint 的本地窥孔(T2 store/load 转发、T3 空 bitcast) |
| `--detection` | 开启 MemBuddy 分配检测(`0xCDCD` 标记 + 记录表探针,查越界写) |
| `--show-args` | 打印全部解析结果 |
| `--version`, `-v` | 打印 Viper / VRTE / ViperC 版本、提交号、目标三元组与 LLVM 版本 |
| `--help`, `-h` | 打印帮助 |
### 架构
```text
source_dir 下全部 .vp/.py(多文件模式:递归扫描所有源文件)
│
├─ Phase 1(stub 分离)
│ IncludesScanner 按需翻译 includes → Phase1/*
│ Translator + Gen/* 生成 IR,再分别落盘:
│ <sha1>.stub.ll 声明
│ <sha1>.text.ll 实现
│
├─ Phase 2(组合 + 编译)
│ StubMerger* 按拓扑序合并 本地 stub + 依赖 stub + 本地 text → 完整 IR
│ BuildPipeline:完整 IR → llc → .obj → clang++(附加 includes 预编译 .obj)→ .exe
│
└─ <build_dir>/output/<linker.output>
```
**核心模块分工:**
| 模块 | 职责 |
| --- | --- |
| `App/main.vp` | CLI 入口:内存池与单例初始化、参数解析、Phase 调度、`--run` |
| `lib/Projectrans/Config.vp` | `project.vpj` 加载、路径解析、includes 缓存目录 |
| `lib/core/CompilerFactory.vp` | 持有 `mb`(主池)与 `perm_pool`(常驻池)两个 `MemBuddy` |
| `lib/core/Gen/Generator.vp` | 项目级 `Generator` 单例,Phase1/Phase2/BuildPipeline 全程复用 |
| `lib/core/Gen/Translator.vp` | 翻译器主体,`translate()` 与 `dump_ir()`(`.stub.ll` / `.text.ll` / 完整 IR) |
| `lib/core/Gen/GenApi.vp` | 翻译器对外接口与全局状态(如 temp 目录) |
| `lib/Phase/Phase1/` | 扫描 includes、拓扑排序、按需生成声明 |
| `lib/Phase/Phase2/` | 多文件工程:递归扫描源文件、翻译、IR 组合、调度编译 |
| `lib/Phase/BuildPipeline.vp` | `.ll` → `llc` → `.obj` → `clang++` → `.exe`(ASan 走插桩分支) |
| `lib/Stub/StubGen/` | 读取 `.vpi`,转换为 stub 声明 |
| `lib/Stub/StubMerger/` | `stub.ll` / `text.ll` 的收集、依赖拓扑、IR 拼接与落盘 |
| `lib/core/IncludesScanner.vp` | 扫描 `includes/`,决定哪些文件需要重编译 |
| `lib/core/VLogger.vp` | 日志系统(含 `--nowarn-register` 开关) |
> 自举相关注意点:自举后模块级全局变量会带 **SHA1 前缀**(模块隔离),因此像 `_g_gen`
> 这类单例必须**逐模块赋值**(`GenApi._g_gen = gen` …),不能只在 `main.vp` 里赋裸名 ——
> 否则部分模块读到自己的 `None`,表现为"全局名判定恒为 0"之类的误报。
### 内存模型
全程无系统 `malloc` 抖动,只有两个 `MemBuddy` 内存池(`lib/memhub` 的伙伴分配器):
- `POOL_SIZE = 1 GiB`(`1073741824`)—— 主池,`mb`,翻译过程的常规分配。
- `PERM_POOL_SIZE = 536871176`(≈512 MiB 可用)—— 常驻池,`perm_pool`,跨阶段存活的对象。
- 两者由 `CompilerFactory.factory` 单例持有;`mb` 另以 `sys._mbuddy` / `argparse._mbuddy` /
`ast._mbuddy` / `Config._mbuddy` … 的形式逐模块注入。
`--detection` 会把两个池都切到检测模式(`0xCDCD` 填充 + 记录表探针),用于排查越界写。
### 与 TransPyC 的关系
| 维度 | TransPyC | ViperC |
| --- | --- | --- |
| 实现语言 | CPython | Viper |
| 定位 | 自举**种子**(只用来生成稳定的 ViperC) | **现行编译器** |
| 自举状态 | — | ✅ 已完成,能编译自身 |
| 入口 | `TransPyC/Projectrans.py` | `App/main.vp` |
| AST 解析 | Python `ast` / 自研 vast | `includes/ast` 自研(9 文件) |
| 工程配置 | `project.json` | `project.vpj` |
| 平台 | Windows / Linux | Windows(`win32.*`) |
### 进一步阅读
- [TransPyC 编译器仓库(种子)](https://git.gvsds.com/GVSDS/TransPyC)
- [Viper 语言文档(wiki 目录)](https://git.gvsds.com/GVSDS/TransPyC/tree/main/wiki)
- [自举可行性分析与进展](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/13-bootstrapping.md)
- [编译系统(工程配置 `project.vpj` / `viperbuild` 元编程)](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/12-compilation-system.md)
- [ViperOS 操作系统(用 Viper 写的 OS)](https://git.gvsds.com/GVSDS/ViperOS)
The **Viper language compiler, written in Viper itself** — and it is **self-hosted**: ViperC compiles its own source and produces `viperc.exe`. It translates Viper sources to LLVM IR, then drives `llc` + `clang++` to link a native Windows executable. The first-generation CPython compiler ([TransPyC](https://git.gvsds.com/GVSDS/TransPyC)) is now only kept as the bootstrapping **seed**.
---
@@ -432,4 +219,4 @@ out-of-bounds writes.
- [Viper language docs (wiki)](https://git.gvsds.com/GVSDS/TransPyC/tree/main/wiki)
- [Bootstrapping analysis and progress](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/13-bootstrapping.md)
- [Compilation system (`project.vpj` / `viperbuild` metaprogramming)](https://git.gvsds.com/GVSDS/TransPyC/blob/main/wiki/12-compilation-system.md)
- [ViperOS (an OS written in Viper)](https://git.gvsds.com/GVSDS/ViperOS)
- [ViperOS (an OS written in Viper)](https://git.gvsds.com/GVSDS/ViperOS)